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