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