当前位置: 移动技术网 > IT编程>开发语言>Java > Disconf源码分析之启动过程分析上(1)

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

2019年04月14日  | 移动技术网IT编程  | 我要评论

夜色撩人 宋雨,小番茄种植,网页模板素材

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网