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的配置文件包括系统自带和用户配置两部分,分别由disclientsysconfig和disclientconfig解析处理,也是单例实现。
// 导入系统配置 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用户配置的思路和系统配置相似,几个不同点:
- disclientconfig属性不同
- 读取的文件路径,除了系统默认的disconf.properties,也可以通过启动参数“-d”来指定配置文件。
- 配置文件读取完成以后,还会通过读取系统参数或命令行导入的属性来覆盖,优先使用该参数。
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包含了disconffilecoreprocessorimpl和disconfitemcoreprocessorimpl的处理器,这一步设计和前面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
上一篇: 不能总开会
推荐阅读
-
分布式定时任务(xxl-job执行器的启动过程源码分析)
-
Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
-
Qt事件分发机制源码分析之QApplication对象构建过程
-
Tomcat源码分析 (六)----- Tomcat 启动过程(一)
-
Bootstrap初始化过程源码分析--netty客户端的启动
-
Java基础之Collections框架Map接口实现类HashMap及其源码分析(1)
-
MyBatis源码分析之——配置解析创建SqlSessionFactory的过程
-
Spring源码分析之与WEB服务器衔接(上)
-
死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程
-
死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程