欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-25 13:07:25
Disconf的启动,主要是包括两次扫描和XML配置生效,总共分为上下两篇,上篇先主要介绍第一次静态扫描过程。 先从入口分析,通过Disconf帮助文档,可以看到xml必须添加如下配置。 DisconfMgrBean 继承了ApplicationContextAware,disconf重载了post ......

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