1. 程式人生 > >Disconf源碼分析之啟動過程分析下(2)

Disconf源碼分析之啟動過程分析下(2)

load() tac 留下 stack date() 監聽 new ron verify

接上文,下面是第二次掃描的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)