【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]王大仙