Disconf源碼分析之啟動過程分析下(2)
接上文,下面是第二次掃描的XML配置。
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
init-method="init" destroy-method="destroy">
</bean>
查看init()方法,會調用DisconfMgr的secondScan()方法。
protected synchronized void secondScan() { // 上面是順序的校驗 try { // 掃描回調函數 if (scanMgr != null) { scanMgr.secondScan(); } // 註入數據至配置實體中 // 獲取數據/註入/Watch if (disconfCoreMgr != null) { disconfCoreMgr.inject2DisconfInstance(); } } catch (Exception e) { LOGGER.error(e.toString(), e); } isSecondInit = true; }
scanMgr是掃描處理器,調用第二次掃描的secondScan()方法。主要處理如下
// 將回調函數實例化並寫入倉庫
ScanDynamicStoreAdapter.scanUpdateCallbacks(scanModel, registry);
ScanDynamicStoreAdapter是動態掃描與Store模塊的轉換器。下面主要對回調函數的處理。
// ScanStaticModel是第一次掃描結束得到的靜態配置存儲的對象 public static void scanUpdateCallbacks(ScanStaticModel scanModel, Registry registry) { // 掃描出來 ScanDynamicModel scanDynamicModel = analysis4DisconfUpdate(scanModel, registry); // 寫到倉庫中 transformUpdateService(scanDynamicModel.getDisconfUpdateServiceInverseIndexMap()); transformPipelineService(scanDynamicModel.getDisconfUpdatePipeline()); }
analysis4DisconfUpdate()會將配置中和回調相關的配置掃描處理。
private static ScanDynamicModel analysis4DisconfUpdate(ScanStaticModel scanModel, Registry registry) { // 配置項或文件,DisconfKey通過配置類型和名稱來標記一個配置key Map<DisconfKey, List<IDisconfUpdate>> inverseMap = new HashMap<DisconfKey, List<IDisconfUpdate>>(); // disconfUpdateService是第一次掃描和回調相關的配置,@DisconfUpdateService註解 Set<Class<?>> disconfUpdateServiceSet = scanModel.getDisconfUpdateService(); for (Class<?> disconfUpdateServiceClass : disconfUpdateServiceSet) { // 回調對應的參數 DisconfUpdateService disconfUpdateService = disconfUpdateServiceClass.getAnnotation(DisconfUpdateService.class); // 校驗是否有繼承正確,是否繼承IDisconfUpdate if (!ScanVerify.hasIDisconfUpdate(disconfUpdateServiceClass)) { continue; } // 獲取回調接口實例 IDisconfUpdate iDisconfUpdate = getIDisconfUpdateInstance(disconfUpdateServiceClass, registry); if (iDisconfUpdate == null) { continue; } // 主要邏輯,將DisconfKey作為key、回調接口作為list vlaue,存入到inverseMap中 // 配置項 processItems(inverseMap, disconfUpdateService, iDisconfUpdate); // // 配置文件 processFiles(inverseMap, disconfUpdateService, iDisconfUpdate); } // set data,存儲所有和回調相關配置結果集合 ScanDynamicModel scanDynamicModel = new ScanDynamicModel(); scanDynamicModel.setDisconfUpdateServiceInverseIndexMap(inverseMap); // // set update pipeline,實現iDisconfUpdatePipeline接口的處理 // if (scanModel.getiDisconfUpdatePipeline() != null) { IDisconfUpdatePipeline iDisconfUpdatePipeline = getIDisconfUpdatePipelineInstance(scanModel .getiDisconfUpdatePipeline(), registry); if (iDisconfUpdatePipeline != null) { // 存儲到scanDynamicModel中 scanDynamicModel.setDisconfUpdatePipeline(iDisconfUpdatePipeline); } } return scanDynamicModel; }
返回以後,會將結果存儲到Store倉庫中。兩種回調方式分別處理。
transformUpdateService(scanDynamicModel.getDisconfUpdateServiceInverseIndexMap());
transformPipelineService(scanDynamicModel.getDisconfUpdatePipeline());
private static void transformUpdateService(Map<DisconfKey,
List<IDisconfUpdate>> disconfUpdateServiceInverseIndexMap) {
// 分別取出配置文件倉庫和配置項倉庫處理器
DisconfStoreProcessor disconfStoreProcessorFile = DisconfStoreProcessorFactory.getDisconfStoreFileProcessor();
DisconfStoreProcessor disconfStoreProcessorItem = DisconfStoreProcessorFactory.getDisconfStoreItemProcessor();
for (DisconfKey disconfKey : disconfUpdateServiceInverseIndexMap.keySet()) {
try {
if (disconfKey.getDisConfigTypeEnum().equals(DisConfigTypeEnum.FILE)) {
// 如果是文件,第一次靜態掃描結束後,肯定會有對應的配置值
if (!disconfStoreProcessorFile.hasThisConf(disconfKey.getKey())) {
throw new Exception();
}
// 存儲到倉庫的回調函數屬性中
disconfStoreProcessorFile.addUpdateCallbackList(disconfKey.getKey(),
disconfUpdateServiceInverseIndexMap
.get(disconfKey));
} else if (disconfKey.getDisConfigTypeEnum().equals(DisConfigTypeEnum.ITEM)) {
// 配置項
if (!disconfStoreProcessorItem.hasThisConf(disconfKey.getKey())) {
throw new Exception();
}
// 存儲到倉庫的回調函數屬性中
disconfStoreProcessorItem.addUpdateCallbackList(disconfKey.getKey(),
disconfUpdateServiceInverseIndexMap
.get(disconfKey));
}
} catch (Exception e) {
// 找不到回調對應的配置,這是用戶配置 錯誤了
StringBuffer sb = new StringBuffer();
sb.append("cannot find " + disconfKey + "for: ");
for (IDisconfUpdate serClass : disconfUpdateServiceInverseIndexMap.get(disconfKey)) {
sb.append(serClass.toString() + "\t");
}
LOGGER.error(sb.toString());
}
}
}
對於pipeline回調函數類似的處理。
繼續第二次掃描。
// 註入數據至配置實體中
// 獲取數據/註入/Watch
if (disconfCoreMgr != null) {
disconfCoreMgr.inject2DisconfInstance();
}
該方法的處理,會分別處理File和item兩項,分別調用DisconfCoreProcessor實現類(和第一次掃描處理類似)
for (DisconfCoreProcessor disconfCoreProcessor : disconfCoreProcessorList) {
disconfCoreProcessor.inject2Conf();
}
下面已File處理為例分析:
inject2Conf()的處理邏輯。
Object object;
try {
object = disconfCenterFile.getObject();
if (object == null) {
// 從上下文獲取實例
object = registry.getFirstByType(disconfCenterFile.getCls(), false, true);
}
} catch (Exception e) {
LOGGER.error(e.toString());
object = null;
}
// 註入實體中
disconfStoreProcessor.inject2Instance(object, fileName);
繼續向下看。
@Override
public void inject2Instance(Object object, String fileName) {
// 先取出配置存儲對象
DisconfCenterFile disconfCenterFile = getInstance().getConfFileMap().get(fileName);
// 校驗是否存在
if (disconfCenterFile == null) {
LOGGER.error("cannot find " + fileName + " in store....");
return;
}
//
// 非靜態類
//
if (object != null) {
// 設置object
disconfCenterFile.setObject(object);
}
// 根據類型設置值
//
// 註入實體
//
Map<String, FileItemValue> keMap = disconfCenterFile.getKeyMaps();
for (String fileItem : keMap.keySet()) {
// 根據類型設置值
try {
//
// 靜態類
//
if (object == null) {
if (keMap.get(fileItem).isStatic()) {
LOGGER.debug(fileItem + " is a static field. ");
keMap.get(fileItem).setValue4StaticFileItem(keMap.get(fileItem).getValue());
}
//
// 非靜態類
//
} else {
LOGGER.debug(fileItem + " is a non-static field. ");
if (keMap.get(fileItem).getValue() == null) {
// 如果倉庫值為空,則實例 直接使用默認值
Object defaultValue = keMap.get(fileItem).getFieldDefaultValue(object);
keMap.get(fileItem).setValue(defaultValue);
} else {
// 如果倉庫裏的值為非空,則實例使用倉庫裏的值
keMap.get(fileItem).setValue4FileItem(object, keMap.get(fileItem).getValue());
}
}
} catch (Exception e) {
LOGGER.error("inject2Instance fileName " + fileName + " " + e.toString(), e);
}
}
}
分別對靜態和非靜態對象屬性賦值。
到這裏位置第二次掃描結束了。
通過兩次掃描加載的數據,都是通過註解式的分布式配置方式,Disconf同時支持XML非註解式配置方式,在上篇介紹的時候,我們留下了關於XML載入的配置處理的分析,下面分析下XML非註解式配置的源碼。
對於非註解式配置,Disconf主要區分為properties文件和非properties文件(properties文件才支持自動reload)、是否自動載入reload到bean對象中(通過XML配置決定)。
我們先分析支持自動reload。
<!-- 使用托管方式的disconf配置(無代碼侵入, 配置更改會自動reload)-->
<bean id="configproperties_disconf"
class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:/autoconfig.properties</value>
<value>classpath:/autoconfig2.properties</value>
</list>
</property>
</bean>
<bean id="propertyConfigurer"
class="com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="propertiesArray">
<list>
<ref bean="configproperties_disconf"/>
</list>
</property>
</bean>
解讀上面的配置前,先了解下Spring提供的PropertyPlaceholderConfigurer類 ,它支持將properties文件中的配置項讀取並在XML中通過#{}的方式讀取,他的觸發是因為實現了BeanFactoryPostProcessor接口,擴展了postProcessBeanFactory方法。
而Disconf就是在此基礎上繼續擴展,ReloadingPropertyPlaceholderConfigurer繼承了PropertyPlaceholderConfigurer類。
首先看下ReloadablePropertiesFactoryBean類,它繼承了PropertiesLoaderSupport類,入口是setLocations()方法。
public void setLocations(List<String> fileNames) {
List<Resource> resources = new ArrayList<Resource>();
for (String filename : fileNames) {
// trim
filename = filename.trim();
String realFileName = getFileName(filename);
//
// register to disconf
// 開始掃描,可以參考上文文件和配置項的掃描
//
DisconfMgr.getInstance().reloadableScan(realFileName);
//
// only properties will reload
//
String ext = FilenameUtils.getExtension(filename);
if (ext.equals("properties")) {
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver =
new PathMatchingResourcePatternResolver();
try {
Resource[] resourceList = pathMatchingResourcePatternResolver.getResources(filename);
for (Resource resource : resourceList) {
resources.add(resource);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
this.locations = resources.toArray(new Resource[resources.size()]);
lastModified = new long[locations.length];
super.setLocations(locations);
}
reloadableScan()方法如下:
public synchronized void reloadableScan(String fileName) {
if (!isFirstInit) {
return;
}
if (DisClientConfig.getInstance().ENABLE_DISCONF) {
try {
// 判斷是不是忽略同步的文件
if (!DisClientConfig.getInstance().getIgnoreDisconfKeySet().contains(fileName)) {
if (scanMgr != null) {
// 掃描配置
scanMgr.reloadableScan(fileName);
}
if (disconfCoreMgr != null) {
// 核心處理器處理
disconfCoreMgr.processFile(fileName);
}
LOGGER.debug("disconf reloadable file: {}", fileName);
}
} catch (Exception e) {
LOGGER.error(e.toString(), e);
}
}
}
scanMgr.reloadableScan()會執行StaticScannerNonAnnotationFileMgrImpl.scanData2Store();
,註意StaticScannerNonAnnotationFileMgrImpl,在上文中,我們介紹了,掃描工具主要包括三種:文件、配置項、非註解配置。在第一次掃描的時候,非註解配置因為list為空(詳細看上文),所以沒有執行後面的邏輯。
繼續看非註解掃描工具處理。
public static void scanData2Store(String fileName) {
// 組裝倉庫對象,和註解文件的不同在於,文件註解本身就是一個對象,非註解配置是一個配置文件,需要轉換為對象。
// 組裝的過程,存在disconfCenterFile.setIsTaggedWithNonAnnotationFile(true);設置。
// 在第一次掃描的時候,有提到,如果是非註解的,該屬性會覆蓋之前註解的倉庫對象
DisconfCenterBaseModel disconfCenterBaseModel =
StaticScannerNonAnnotationFileMgrImpl.getDisconfCenterFile(fileName);
// 因為非註解配置肯定文件,所以調用文件倉庫處理器,後面的邏輯參考上文 DisconfStoreProcessorFactory.getDisconfStoreFileProcessor().transformScanData(disconfCenterBaseModel);
}
掃描完成以後,開始核心處理器處理。
/**
* 只處理某一個
*/
@Override
public void processFile(String fileName) {
// 獲取配置文件核心處理器,原理和上文一樣
DisconfCoreProcessor disconfCoreProcessorFile =
DisconfCoreProcessorFactory.getDisconfCoreProcessorFile(watchMgr, fetcherMgr, registry);
// 在第一次掃描的時候會調用processAllItems()處理,但是xml配置的掃描肯定是單個的,所以直接調用單個處理
disconfCoreProcessorFile.processOneItem(fileName);
}
再後面的處理和第一次掃描的處理是同一個方法,一個配置文件, 下載、註入到倉庫、Watch 三步驟。
繼續XML配置解析,對於properties類型的文件,Spring的PropertyPlaceholderConfigurer類支持處理,所以最後將properties類型的文件設置到父類的locations屬性中。setLocations()結束。
因為ReloadablePropertiesFactoryBean繼承自PropertiesFactoryBean,PropertiesFactoryBean實現了InitializingBean接口,所以在初始化的時候,會調用afterPropertiesSet()方法。
public final void afterPropertiesSet() throws IOException {
if(this.singleton) {
this.singletonInstance = this.createProperties();
}
}
protected Properties createProperties() throws IOException {
return this.mergeProperties();
}
而ReloadablePropertiesFactoryBean重載了createProperties()方法。
@Override
protected Properties createProperties() throws IOException {
return (Properties) createMyInstance();
}
/**
* @throws IOException
*/
protected Object createMyInstance() throws IOException {
// would like to uninherit from AbstractFactoryBean (but it's final!)
if (!isSingleton()) {
throw new RuntimeException("ReloadablePropertiesFactoryBean only works as singleton");
}
// set listener
reloadableProperties = new ReloadablePropertiesImpl();
if (preListeners != null) {
reloadableProperties.setListeners(preListeners);
}
// reload
reload(true);
// add for monitor
ReloadConfigurationMonitor.addReconfigurableBean((ReconfigurableBean) reloadableProperties);
return reloadableProperties;
}
首先看ReloadablePropertiesImpl的實現,他繼承自ReloadablePropertiesBase,包含了List<IReloadablePropertiesListener> listeners
監聽列表。開始preListeners默認為null,直接執行reload(true)
,默認情況下reload方法通過判斷配置文件的修改時間來確認是否重新加載,這裏因為傳參為true,所以強制reload()
。調用ReloadablePropertiesBase的setProperties()。
/**
* 通過listener去通知 reload
*
* @param oldProperties
*/
protected void notifyPropertiesChanged(Properties oldProperties) {
PropertiesReloadedEvent event = new PropertiesReloadedEvent(this, oldProperties);
for (IReloadablePropertiesListener listener : listeners) {
listener.propertiesReloaded(event);
}
}
/**
* set value 觸發
*
* @param properties
*/
protected void setProperties(Properties properties) {
Properties oldProperties = internalProperties;
synchronized(this) {
internalProperties = properties;
}
notifyPropertiesChanged(oldProperties);
}
可以看到最後會遍歷前面所說的listeners列表,如果有值的情況會調用listener的propertiesReloaded()方法去reload。IReloadablePropertiesListener接口的實現類是ReloadingPropertyPlaceholderConfigurer。
有了ReloadablePropertiesFactoryBean以後,Disconf支持兩種非註解式處理,分別的Spring自帶的PropertyPlaceholderConfigurer和ReloadingPropertyPlaceholderConfigurer。兩者的區別是會不會自動reload。結合上面所說,如果想要自動reload,就是通過listeners列表實現。如果使用Spring自帶的PropertyPlaceholderConfigurer,那麽自然就不會有listener。
當我們使用ReloadingPropertyPlaceholderConfigurer的作為XML配置時,因為實現了InitializingBean接口,所以會執行afterPropertiesSet()。
/**
* afterPropertiesSet
* 將自己 添加 property listener
*/
public void afterPropertiesSet() {
for (Properties properties : propertiesArray) {
if (properties instanceof ReloadableProperties) {
logger.debug("add property listener: " + properties.toString());
// addReloadablePropertiesListener執行了listeners.add()。
((ReloadableProperties) properties).addReloadablePropertiesListener(this);
}
}
}
在加載ReloadablePropertiesFactoryBean的時候,我們已經把所有的properties格式的文件放入到propertiesArray,所以都會加入到listener中,最後會調用propertiesReloaded()進行處理。
至此,Disconf的啟動過程分析結束。
轉載請註明出處。
作者:wuxiwei
出處:https://www.cnblogs.com/wxw16/p/10741202.html
Disconf源碼分析之啟動過程分析下(2)