1. 程式人生 > >【CSII-PE】dmconfig.xml實現原理及配置方法

【CSII-PE】dmconfig.xml實現原理及配置方法

在一部分bundle的程式碼中(例如com.csii.gateway.api),在META-INF目錄下有個peconfig目錄,其中有一個dmconfig.xml檔案。這個檔案中有一些配置資訊:

<?xml version="1.0" encoding="UTF-8" ?>
<dmconfig>
	<propdef>
		<scope>gateway api Config</scope>

		<scopenames>
			<en_US>gateway api Config</en_US>
			<zh_CN>gateway api 資訊配置</zh_CN>
		</scopenames>


		<properties>
			<propdefentry name="authTime.authTimeRange">
				<type>String</type>
				<right>RW</right>
				<allowedValues>
					<value></value>
				</allowedValues>
				<propnames>
					<en_US>authTime.authTimeRange</en_US>
					<zh_CN>聯網核查稽核時間範圍</zh_CN>
				</propnames>
				<defaultValue>08:00:00-19:00:00</defaultValue>
			</propdefentry>
		</properties>
	</propdef>
</dmconfig>

然後我們在config目錄下的xml配置檔案中就可以使用Properties佔位符的形式來引用在dmconfig.xml中配置的資料:

<action id="LoanApplyAction" class="com.csii.gateway.api.loan.LoanApplyAction" parent="BaseTwoPhaseAction">
		<ref name="loanAppSeqIdFactory">loanAppSeqIdFactory</ref>
		<ref name="declNoSeqIdFactory">declNoSeqIdFactory</ref>
		<param name="authTimeRange">${authTime.authTimeRange}</param>
	</action>

通過一些學習,我們已經知道要使用spring來管理我們的bundle中的bean,要麼在META-INF目錄下新增一個spring目錄來存放spring的配置檔案,要麼在MANIFEST.MF檔案中指定Spring-Context頭資訊,我們的pe框架採取的是第二種方式:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: com.csii.gateway.api
Bundle-SymbolicName: com.csii.gateway.api
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: CSII
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: pedynamic;bundle-version="6.0.0",
 pedynamicweb;bundle-version="6.0.0",
 com.csii.pe.uibs;bundle-version="6.0.0",
 com.csii.pe.report.impl;bundle-version="1.0.0",
 com.csii.gateway.dynamic.ibs;bundle-version="1.0.0",
 com.csii.gateway.common;bundle-version="1.0.0",
 com.csii.ecif.datamodel;bundle-version="6.1.0"
Spring-Context: META-INF/config/*.xml,META-INF/config/trs/*.xml,META-INF/config/bop/*.xml
CSII-AutoStart: true
CSII-WebModule: api
CSII-StartLevel: 8
Import-Package: com.csii.gateway.core.filter,
 com.csii.gateway.core.util,
 com.csii.mcs.constants,
 com.csii.mcs.datamodel
Bundle-Activator: com.csii.pe.dynamic.core.CSIIActivator
Bundle-ClassPath: .,
 lib/dom4j-1.6.1.jar,
 lib/PaperlessClient.jar,
 lib/SADK-3.2.1.2.jar,
 lib/SealSADK-3.1.1.6.jar,
 lib/fastjson-1.1.41.jar

這裡可以看到,指定的spring配置檔案目錄是config,而peconfig不在這個範圍之內,那麼也就說明這個dmconfig.xml不會在bundle啟動時由spring來處理。但是我們config目錄下的xml配置檔案確實是由spring來處理的,並且其中的properties佔位符也是由spring來解析的。既然這樣,那麼spring是怎麼把配置在dmconfig.xml中的資訊正確解析出來呢?

檢視config目錄下,有個peconfig.xml檔案,配置檔案中屬性佔位符的解析就配置在這個檔案中:

<bean id="placeholderConfig" class="com.csii.pe.dynamic.core.CMPropertyPlaceholderConfigurer">
     <ref name="bundleContext" >bundleContext</ref>
     <list name="locations">
         <param>classpath:/META-INF/config/base/common.properties</param>
     </list>
</bean>

這裡配置的class本應該是spring提供的類,但是這裡使用的卻是pe提供的類,那麼可以推測這個類必定繼承了spring提供的類,並且做了一些擴充套件(這裡還設定了bundleContext)。

通過檢視CMPropertyPlaceholderConfigurer類的原始碼,看到他集成了spring提供的PropertyPlaceholderConfigurer類,並且重寫了mergeProperties方法:

protected Properties mergeProperties() throws IOException{
    Properties constantProp = super.mergeProperties();

    ServiceReference[] serviceReferences = this.bundleContext.getBundle().getRegisteredServices();
    if (serviceReferences == null)
    {
      throw new RuntimeException("CMPropertyConfigService is not registered");
    }

    String myPid = this.bundleContext.getBundle().getSymbolicName() + ".DMCONFIG";
    for (ServiceReference serviceReference : serviceReferences)
    {
      String pid = (String)serviceReference.getProperty("service.pid");

      if ((pid == null) || (!(pid.equals(myPid))))
        continue;
      PropertyConfigService propertyConfigService = (PropertyConfigService)this.bundleContext.getService(serviceReference);
      Properties customProp = propertyConfigService.getProperties(constantProp);

      while (customProp == null)
      {
        this.log.error("Wait 3 seconds for ConfigManage Service go alive...");
        try {
          Thread.sleep(3000L);
        }
        catch (InterruptedException localInterruptedException) {
        }
        customProp = propertyConfigService.getProperties(constantProp);
      }

      this.bundleContext.ungetService(serviceReference);

      if (this.decryptFieldNames != null)
      {
        for (int i = 0; i < this.decryptFieldNames.length; ++i)
        {
          if (this.decryptModule != null)
          {
            String cipherText = customProp.getProperty(this.decryptFieldNames[i]);
            String pin = this.decryptModule.decrypt(this.decryptFieldNames[i], cipherText);
            customProp.setProperty(this.decryptFieldNames[i], pin);
          }
          else
          {
            this.log.error("pls_config_decrypt_module");
          }

        }

      }
      return customProp;
    }
    throw new RuntimeException("CMProertyConfigService is not registered");
}

這裡可以看到,列舉了當前bundle註冊的所有Service,根據service.pid來匹配相應的服務,目標Service的型別為PropertyConfigService,最後呼叫了該Service的getProperties方法獲取到了一個Properties物件,這個Properties物件中的屬性資料,就是spring解析配置檔案中屬性佔位符的資料來源。

那麼說明,dmconfig.xml中的資料可以解析,必定實在PropertyConfigService中進行了合併。現在就來找PropertyConfigService的實現:

它只有一個實現類:CMPropertyConfigService:

這個類不僅實現了PropertyConfigService介面,還實現了OSGI中提供的ManageService介面。該介面屬於Configuration Admin Service規範(後面再講),該規範主要用於將一些bundle的引數可配置化,也就是在在bundle之外來進行配置一些引數。需要進行引數化配置的bundle需要註冊一個ManagedService服務,並且實現update方法和指定service.pid屬性,當外部的配置發生變化時,框架會根據service.pid來找到註冊的managedService物件,並且呼叫她的update方法,將新的引數傳遞進來,我們的bundle只需要在update方法中獲取新的引數,並且更新當前Bundle的狀態即可。

檢視CMPropertyConfigService類的構造方法:

public CMPropertyConfigService(BundleContext bundleContext){
    this.bundleContext = bundleContext;
    try
    {
      //省略部分程式碼
      this.xmlParser = new XmlStreamParser();
      this.xmlParser.setResourceLoader(resourceLoader);
      this.xmlParser.setUsingRLCL(true);
      this.xmlParser.setTagClassMapping("/META-INF/dmconfig/xmltagmapping.properties");
      this.xmlParser.setTagAliasMapping("/META-INF/dmconfig/xmlaliasmapping.properties");
      this.xmlParser.afterPropertiesSet();

      this.dmconfig = parseDMConfig();
    }
    catch (Exception e)
    {
      throw new PeRuntimeException(e);
    }
}

構造方法中可以看到,初始化了一個xml解析器。紅色標註部分的關鍵程式碼,呼叫parseDMconfig方法的來解析dmconfig.xml檔案,解析結果儲存在dmconfig成員變數中。

private List parseDMConfig()throws TransformException, IOException{
    URL url = this.bundleContext.getBundle().getResource("/META-INF/peconfig/dmconfig.xml");
    InputStream in = url.openStream();
    try{
      return ((List)this.xmlParser.parse(in, null));
    }
    finally
    {
      try {
        in.close();
      }
      catch (IOException localIOException1){
      }
    }
 }

這裡就可以看到,dmconfig.xml檔案中的資料就被解析出來了,那麼接著就是看ManagedService介面的update方法:

public void updated(Dictionary dictionary)throws ConfigurationException{
    if (dictionary != null){
      for (PropDef propDef : this.dmconfig){
        propDef.update(dictionary);
      }
    }
    this.customConfigLoaded = true;
}

在update方法中,將之前dmconfig.xml中解析出來的資料進行了更新,只有在dmconfig.xml中配置的key的值才會更新,其他的會忽略。

最後來看PropertyConfigService介面的getProperties方法:

public Properties getProperties(Properties initProps)throws IOException{
    if (this.customConfigLoaded){
      if (initProps == null) {
        initProps = new Properties();
      }
      Iterator localIterator1 = this.dmconfig.iterator(); 
      while (true) {
        PropDef propDef = (PropDef)localIterator1.next();
        Map propDefEntries = propDef.getPropDefEntries();

        for (Iterator it = propDefEntries.entrySet().iterator(); it.hasNext(); ){
          Map.Entry entry = (Map.Entry)it.next();

          Object key = entry.getKey();
          PropDefEntry propDefEntry = (PropDefEntry)entry.getValue();
          if (propDefEntry.getValue() != null)
            initProps.put(key, propDefEntry.getValue());
        }
        if (!(localIterator1.hasNext())){
          return initProps;
        }
      }

    }

    //省略部分程式碼
}

這個方法中將已經解析出來的Proerpties物件傳遞進來,然後再附加上dmconfig.xml中配置的屬性。最後返回的屬性物件包含在peconfig.xml中配置的Properties檔案和在dmconfig.xml中配置的動態引數。

注:使用dmconfig.xml的目的是為了能夠在bundle外部修改這些引數,我們公司提供的peconsole產品就可以動態的修改dmconfig.xml中配置的引數,而不需要update這些bundle.

補充:從上面的分析可以看到,通過引用ManagedService/PropertyConfigService服務,將dmconfig.xml中的內容動態更新進來。但是上面的內容沒有說明這個ManagedService是在何處釋出出來的。可以看到Bundle配置了一個Activator,對應的實現類是CSIIActivator,檢視它的start方法:

public void start(BundleContext bundleContext)throws Exception{
    PropertyConfigService propertyConfigService = new CMPropertyConfigService(bundleContext);
    Properties props = new Properties();

    props.put("service.pid", bundleContext.getBundle().getSymbolicName() + ".DMCONFIG");

    this.propertyConfigServiceRegistration = bundleContext.registerService(
      new String[] { 
      PropertyConfigService.class.getName(), 
      ManagedService.class.getName() }, 
      propertyConfigService, props);
}

從這段程式碼中就可以看到,在該Bundle啟動時,Activator註冊了ManagedService服務,同時也是PropertyConfigService,這也就是為什麼在上面的CMPropertyPlaceholderConfigurer中能夠根據service.pid拿到PropertyConfigService的原因。

總結:

要想通過PEConsole來動態修改程式的配置資訊,需要以下幾點:

1.config目錄下配置CMPropertyPlaceholderConfigurer(peconfig.xml)

2.META-INF目錄下新增peconfig目錄,同時新增dmconfig.xml

3.該Bundle使用Activator:com.csii.pe.dynamic.core.CSIIActivator

by [email protected]王大仙