zoukankan      html  css  js  c++  java
  • Disconf源码分析之启动过程分析上(1)

    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

  • 相关阅读:
    数据库基本概念
    Python语言特性之5:自省
    Python语言特性之4:类变量和实例变量
    Python语言特性之3:@staticmethod和@classmethod
    Python语言特性之2:元类
    Python语言特性之1:函数参数传递
    基础数学与算法学习
    推荐系统资料
    MySQL相关
    Python科学计算包模块的安装(ubuntu)
  • 原文地址:https://www.cnblogs.com/wxw16/p/10701673.html
Copyright © 2011-2022 走看看