【CSII-PE】dmconfig.xml实现原理及配置方法

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 CSII@王大仙

猜你喜欢

转载自blog.csdn.net/joxlin/article/details/81625223