Disconf源码分析之启动过程分析上(1)

时间:2021-11-01 04:23:43

Disconf的启动,主要是包括两次扫描和XML非注解式配置,总共分为上下两篇,上篇先主要介绍第一次静态扫描过程。

先从入口分析,通过Disconf帮助文档,可以看到xml必须添加如下配置。

<!-- 使用disconf必须添加以下配置 -->
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
      destroy-method="destroy">
    <property name="scanPackage" value="com.demo.disconf"/>
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
      init-method="init" destroy-method="destroy">
</bean>

DisconfMgrBean继承了ApplicationContextAware,disconf重载了postProcessBeanDefinitionRegistry()实现第一次加载。

/**
 * 第一次扫描<br/>
 * 在Spring内部的Bean定义初始化后执行,这样是最高优先级的
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

    // 为了做兼容
    DisconfCenterHostFilesStore.getInstance().addJustHostFileSet(fileList);

    // 从2.6.23开始,disconf支持scanPackage传递多个包路径,通过“,”分割,然后会切割包文件并去重存入到list中。
    List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);
    // unique
    Set<String> hs = new HashSet<String>();
    hs.addAll(scanPackList);
    scanPackList.clear();
    scanPackList.addAll(hs);

    // 通过静态提前加载的单例方式获取DisconfMgr,并设置applicationContext上下文
    DisconfMgr.getInstance().setApplicationContext(applicationContext);
    // 进行扫描
    DisconfMgr.getInstance().firstScan(scanPackList);

    // register java bean
    registerAspect(registry);
}

看下DisconfMgr的firstScan()方法。

/**
 * 第一次扫描,静态扫描 for annotation config
 */
protected synchronized void firstScan(List<String> scanPackageList) {

    // isFirstInit用来判断第一次加载,结合synchronized加锁来保证
    // 该函数不能调用两次
    if (isFirstInit) {
        LOGGER.info("DisConfMgr has been init, ignore........");
        return;
    }

    try {
        // 导入配置
        ConfigMgr.init();
        // registry
        Registry registry = RegistryFactory.getSpringRegistry(applicationContext);

        // 扫描器
        scanMgr = ScanFactory.getScanMgr(registry);

        // 第一次扫描并入库
        scanMgr.firstScan(scanPackageList);

        // 获取数据/注入/Watch
        disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
        disconfCoreMgr.process();

        //
        isFirstInit = true
    } catch (Exception e) {
        LOGGER.error(e.toString(), e);
    }
}

先看ConfigMgr.init();的实现,disconf的配置文件包括系统自带和用户配置两部分,分别由DisClientSysConfigDisClientConfig解析处理,也是单例实现。

// 导入系统配置
DisClientSysConfig.getInstance().loadConfig(null);
// 校验 系统配置
DisInnerConfigHelper.verifySysConfig();
// 导入用户配置
DisClientConfig.getInstance().loadConfig(null);
// 校验 用户配置
DisInnerConfigHelper.verifyUserConfig();
isInit = true;

先看DisClientSysConfig所有属性都有注解@DisInnerConfigAnnotation,在后面的配置过程中,会通过反射的方式来赋值。

/**
 * STORE URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_store_action")
public String CONF_SERVER_STORE_ACTION;

/**
 * STORE URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_zoo_action")
public String CONF_SERVER_ZOO_ACTION;

/**
 * 获取远程主机个数的URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_master_num_action")
public String CONF_SERVER_MASTER_NUM_ACTION;

/**
 * 下载文件夹, 远程文件下载后会放在这里
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.local_download_dir")
public String LOCAL_DOWNLOAD_DIR;

DisClientSysConfig的loadConfig方法,会去调用DisconfAutowareConfig.autowareConfig()方法。默认的系统配置文件为disconf_sys.properties。

DisconfAutowareConfig主要的实现思路是通过DisconfAutowareConfig将配置的内容到导入到Properties中,然后通过反射的方式将上面DisClientSysConfig的各个属性和配置对应赋值。

最后通过DisInnerConfigHelper.verifySysConfig()进行配置校验。

DisClientConfig用户配置的思路和系统配置相似,几个不同点:

  1. DisClientConfig属性不同
  2. 读取的文件路径,除了系统默认的disconf.properties,也可以通过启动参数“-d”来指定配置文件。
  3. 配置文件读取完成以后,还会通过读取系统参数或命令行导入的属性来覆盖,优先使用该参数。

Config配置文件读取完毕,继续扫描工作。

// registry
// 将上下文处理成上下文处理工具
Registry registry = RegistryFactory.getSpringRegistry(applicationContext);

// 扫描器
// 通过扫描器工厂,获取扫描器工具
scanMgr = ScanFactory.getScanMgr(registry);

// 第一次扫描并入库
scanMgr.firstScan(scanPackageList);

// 获取数据/注入/Watch
disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
disconfCoreMgr.process();

ScanFactory工厂得到的ScanMgrImpl构造函数有很多信息,ScanMgrImpl作为扫描模块的中心。

public ScanMgrImpl(Registry registry) {
    this.registry = registry;
    // 配置文件
    staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfFileStaticScanner());
    // 配置项
    staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfItemStaticScanner());
    // 非注解 托管的配置文件
    staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfNonAnnotationFileStaticScanner());
}

除了registry上下文的赋值,还包括将三种配置类型的扫描工具,载入到staticScannerMgrList中,在后面的扫描过程中,会遍历工具分别处理Disconf上的配置。

开始扫描静态文件,并且将结果存错到store中。(DisconfCenterStore作为配置仓库中心,后面会介绍)

/**
 * 扫描并存储(静态)
 */
public void firstScan(List<String> packageNameList) throws Exception {
    // 获取扫描对象并分析整合
    scanModel = scanStaticStrategy.scan(packageNameList);
    // 增加非注解的配置
    scanModel.setJustHostFiles(DisconfCenterHostFilesStore.getInstance().getJustHostFiles());
    // 放进仓库
    for (StaticScannerMgr scannerMgr : staticScannerMgrList) {
        // 扫描进入仓库
        scannerMgr.scanData2Store(scanModel);
        // 忽略哪些KEY
        scannerMgr.exclude(DisClientConfig.getInstance().getIgnoreDisconfKeySet());
    }
}

ScanStaticModel的作用是配置扫描内容的存储对象。

ReflectionScanStatic将路径下文件扫描得到静态注解,并整合到ScanStaticModel中。ReflectionScanStatic的主要处理方式是通过反射将Disconf支持的所有注解获取到(具体的可以查看帮助文档),初步扫描以后会进行解析,赋值到ScanStaticModel对象中。

获取到静态扫描的scanModel后,添加非注解的配置,这部分官方给出注释说已经废弃,只是兼容作用。

最后是对staticScannerMgrList的遍历,通过各种类型的扫描工具,分别处理scanModel,将结果添加到DisconfCenterStore仓库中。

看下扫描工具的实现,拿StaticScannerFileMgrImpl举例。

继承自StaticScannerMgr接口,实现了scanData2Store()和exclude()方法,scanData2Store()扫描数据并写入仓库,exclude()将Config指定的忽略配置内容从仓库中移除。

@Override
public void scanData2Store(ScanStaticModel scanModel) {
    // 转换配置文件
    List<DisconfCenterBaseModel> disconfCenterFiles = getDisconfFiles(scanModel);
    DisconfStoreProcessorFactory.getDisconfStoreFileProcessor().transformScanData(disconfCenterFiles);
}

首先会将scanModel转回成List<DisconfCenterBaseModel>(File和Item的转换是不同的,具体看源码),文件配置是DisconfCenterFile,继承自DisconfCenterBaseModel。然后依赖仓库配置文件的处理工具DisconfStoreFileProcessorImpl,进行配置文件的处理和提交到仓库。

看下仓库工具的实现,拿DisconfStoreFileProcessorImpl举例。

仓库工具继承了DisconfStoreProcessor接口,transformScanData()遍历仓库配置元素,调用DisconfCenterStore.getInstance()获取单例DisconfCenterStore,调用storeOneFile()进行存储配置。

/**
 * 存储 一个配置文件
 */
public void storeOneFile(DisconfCenterBaseModel disconfCenterBaseModel) {
    DisconfCenterFile disconfCenterFile = (DisconfCenterFile) disconfCenterBaseModel;
    String fileName = disconfCenterFile.getFileName();
    if (confFileMap.containsKey(fileName)) {
        LOGGER.warn("There are two same fileName key!!!! " + fileName);
        DisconfCenterFile existCenterFile = confFileMap.get(fileName);
        // 如果是 同时使用了 注解式 和 非注解式 两种方式,则当修改时也要 进行 XML 式 reload
        if (disconfCenterFile.isTaggedWithNonAnnotationFile()) {
            // 该参数用于判断是不是非注解式(托管式),设置为true,在配置更新时,会进行xml的reload
            existCenterFile.setIsTaggedWithNonAnnotationFile(true);
        }
    } else {
        confFileMap.put(fileName, disconfCenterFile);
    }
}

扫描工具StaticScannerItemMgrImpl和File的实现是相似的,同样会有仓库工具DisconfStoreItemProcessorImpl进行存储。

对于非注解的扫描工具StaticScannerNonAnnotationFileMgrImpl,在当前的启动过程中,不会存在该处理器需要处理的元素,后面介绍这个工具的使用。

继续第一次扫描,目前为止已经将所有的注解配置都载入到仓库中,仓库中主要包含了哪些配置类、配置项等等,后面就可以根据这些元素,从Disconf服务端获取配置值、注入到Bean,以及监听配置更新并动态Reload。

// 获取数据/注入/Watch
disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
disconfCoreMgr.process();

DisconfCoreFactory作为DisconfCoreMgr的工厂类,DisconfCoreMgr包含了FetcherMgr下载模块和WatchMgrImpl模块,主要依赖disconf-core中Zookeeper和Restful通用工具类。

DisconfCoreMgrImpl是核心处理器,构造函数进行赋值。

private List<DisconfCoreProcessor> disconfCoreProcessorList = new ArrayList<DisconfCoreProcessor>();
// 监控器
private WatchMgr watchMgr = null;
// 抓取器
private FetcherMgr fetcherMgr = null;
// registry
private Registry registry = null;
public DisconfCoreMgrImpl(WatchMgr watchMgr, FetcherMgr fetcherMgr, Registry registry) {
    this.watchMgr = watchMgr;
    this.fetcherMgr = fetcherMgr;
    this.registry = registry;
    //在这里添加好配置项、配置文件的处理器
    DisconfCoreProcessor disconfCoreProcessorFile = DisconfCoreProcessorFactory.getDisconfCoreProcessorFile(watchMgr, fetcherMgr, registry);
    disconfCoreProcessorList.add(disconfCoreProcessorFile);
    DisconfCoreProcessor disconfCoreProcessorItem = DisconfCoreProcessorFactory.getDisconfCoreProcessorItem(watchMgr, fetcherMgr, registry);
    disconfCoreProcessorList.add(disconfCoreProcessorItem);
}

disconfCoreProcessorList包含了DisconfFileCoreProcessorImplDisconfItemCoreProcessorImpl的处理器,这一步设计和前面File和Item的Scan扫描处理类似,处理器实现了DisconfCoreProcessor接口 。

disconfCoreMgr.process();遍历两种处理器,调用processAllItems()处理。看处理器的具体任务,DisconfFileCoreProcessorImpl举例说明。

@Override
public void processAllItems() {

    /**
     * 配置文件列表处理
     * disconfStoreProcessor具体实现是DisconfStoreFileProcessorImpl
     */
    for (String fileName : disconfStoreProcessor.getConfKeySet()) {
        // 获取所有的配置文件名
        processOneItem(fileName);
    }
}

@Override
public void processOneItem(String key) {
    // 获取仓库中的元素
    DisconfCenterFile disconfCenterFile = (DisconfCenterFile) disconfStoreProcessor.getConfData(key);
    try {
        updateOneConfFile(key, disconfCenterFile);
    } catch (Exception e) {
        LOGGER.error(e.toString(), e);
    }
}

/**
 * 更新 一個配置文件, 下载、注入到仓库、Watch 三步骤
 */
private void updateOneConfFile(String fileName, DisconfCenterFile disconfCenterFile) throws Exception {

    if (disconfCenterFile == null) {
        throw new Exception("cannot find disconfCenterFile " + fileName);
    }

    String filePath = fileName;
    Map<String, Object> dataMap = new HashMap<String, Object>();

    //
    // 开启disconf才需要远程下载, 否则就本地就好
    //
    if (DisClientConfig.getInstance().ENABLE_DISCONF) {

        //
        // 下载配置
        //
        try {
            // 先获取配置的路径,在载入时可以看到。
            String url = disconfCenterFile.getRemoteServerUrl();
            // 该方法主要是通过url进行文档下载,具体里面的实现,会将配置文件下载并移动都指定路径。
            filePath = fetcherMgr.downloadFileFromServer(url, fileName, disconfCenterFile.getFileDir());

        } catch (Exception e) {

            //
            // 下载失败了, 尝试使用本地的配置
            //
            LOGGER.error(e.toString(), e);
            LOGGER.warn("using local properties in class path: " + fileName);

            // change file path
            filePath = fileName;
        }
        LOGGER.debug("download ok.");
    }

    try {
        // FileTypeProcessorUtils配置处理器,根据配置项的类型和文件路径,读取配置值到Map,总共有*、xml、properties,目前只有对properties类型会做解析,返回Map。
        dataMap = FileTypeProcessorUtils.getKvMap(disconfCenterFile.getSupportFileTypeEnum(),
                disconfCenterFile.getFilePath());
    } catch (Exception e) {
        LOGGER.error("cannot get kv data for " + filePath, e);
    }

    //
    // 注入到仓库中
    //
    disconfStoreProcessor.inject2Store(fileName, new DisconfValue(null, dataMap));
    LOGGER.debug("inject ok.");

    //
    // 开启disconf才需要进行watch
    //
    if (DisClientConfig.getInstance().ENABLE_DISCONF) {
        //
        // Watch
        //
        DisConfCommonModel disConfCommonModel = disconfStoreProcessor.getCommonModel(fileName);
        if (watchMgr != null) {
            watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                    GsonUtils.toJson(disconfCenterFile.getKV()));
            LOGGER.debug("watch ok.");
        } else {
            LOGGER.warn("cannot monitor {} because watch mgr is null", fileName);
        }
    }
}

看下inject2Store()的处理:

public void inject2Store(String fileName, DisconfValue disconfValue) {

    // 获取配置中心对象
    DisconfCenterFile disconfCenterFile = getInstance().getConfFileMap().get(fileName);
    // 校验是否存在
    if (disconfCenterFile == null) {
        LOGGER.error("cannot find " + fileName + " in store....");
        return;
    }
    if (disconfValue == null || disconfValue.getFileData() == null) {
        LOGGER.error("value is null for {}", fileName);
        return;
    }
    // 存储、将dataMap的值存储到对象的属性上
    Map<String, FileItemValue> keMap = disconfCenterFile.getKeyMaps();
    if (keMap.size() > 0) {
        for (String fileItem : keMap.keySet()) {

            Object object = disconfValue.getFileData().get(fileItem);
            if (object == null) {
                LOGGER.error("cannot find {} to be injected. file content is: {}", fileItem,
                        disconfValue.getFileData().toString());
                continue;
            }

            // 根据类型设置值
            try {

                Object value = keMap.get(fileItem).getFieldValueByType(object);
                keMap.get(fileItem).setValue(value);

            } catch (Exception e) {
                LOGGER.error("inject2Store filename: " + fileName + " " + e.toString(), e);
            }
        }
    }
    // 使用过 XML式配置
    if (disconfCenterFile.isTaggedWithNonAnnotationFile()) {
        if (disconfCenterFile.getSupportFileTypeEnum().equals(SupportFileTypeEnum.PROPERTIES)) {
            // 如果是采用XML进行配置的,则需要利用spring的reload将数据reload到bean里
            ReloadConfigurationMonitor.reload();
        }
        disconfCenterFile.setAdditionalKeyMaps(disconfValue.getFileData());
    }
}

还是比较清晰的,会判断仓库中存储对象的状态,然后将相应的对象值进行存储。下面对于XML式配置的处理,暂时不做介绍,后面介绍。

仓库中的配置处理完成以后,如果需要监听配置文件的更新,那么就需要通过Watch去做监控,监控的目标主体是由配置app名、版本号、env环境构成的对象DisConfCommonModel以及其他信息。disConfCommonModel在scanData2Store()中转换配置文件的时候已经存储到仓库对象中。

下面看下最后一行代码:

watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                    GsonUtils.toJson(disconfCenterFile.getKV()));

我们主要看前面两个参数,第二个是刚刚说的监控对象,第一个参数是this自身。

public void watchPath(DisconfCoreProcessor disconfCoreMgr, DisConfCommonModel disConfCommonModel, String keyName,
                          DisConfigTypeEnum disConfigTypeEnum, String value) throws Exception {
        // 新建
        String monitorPath = makeMonitorPath(disConfigTypeEnum, disConfCommonModel, keyName, value);
        // 进行监控
        NodeWatcher nodeWatcher =
                new NodeWatcher(disconfCoreMgr, monitorPath, keyName, disConfigTypeEnum, new DisconfSysUpdateCallback(),
                        debug);
        nodeWatcher.monitorMaster();
    }

因为分布式一致性是通过zookeeper实现的,节点模型需要将disConfCommonModel对象和其他信息转换成目录路径monitorPath。

NodeWatcher是zookeeper api接口Watcher的实现,可以看到disconfCoreMgr会被设置为NodeWatcher属性,当Watcher在配置更新监控到消息,执行监听代码是,会调用new DisconfSysUpdateCallback()的reload()方法,而reload()方法会调用disconfCoreMgr的updateOneConfAndCallback()方法。具体的回调操作后面介绍。

以上是DisconfFileCoreProcessorImpl的实现逻辑,DisconfFItemCoreProcessorImpl的逻辑也是类似的。

回到最开始第一次静态扫描入口,最后剩下bean注入。

registerAspect(registry);

手动注入DisconfAspectJ.class对象。通过AOP拦截方式,用于获取配置文件和配置项。

至此第一次静态扫描工作结束,分析过程有一些简单的代码实现和跟踪,没有详细的解说,可以自行查看源码。

转载请注明出处。
作者:wuxiwei
出处:https://www.cnblogs.com/wxw16/p/10701673.html