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