天天看點

基于OSGi的企業級開發架構實踐——全局參數工具

在接下去的幾篇文章中,我們将通過一些最佳實踐來繼續讨論我們的開發架構。做為一個應用系統,全局參數配置是一個不可或缺的重要工具。它可以為系統初始化提供必須的參數,也可以為系統提供一些使用者自定義的個性化參數,總之它關系到整個應用系統的正确運作。在上一篇文章中,我們已經認識了它,現在就讓我們更加深入的了解它的廬山真面目。當你啟動了開發架構的運作時環境後,會在Eclipse Console中看到如下圖所示的日志資訊:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖一)

在開發架構運作時容器啟動的時候會自動的加載指定目錄下的system.properties檔案,該檔案就是預設的全局參數配置檔案,比如上例中的D:\home\admin\share\data\helloworld\system.properties。在assembly目錄下提供了一個system.properties檔案的預設模闆,請在第一次啟動開發架構時複制該檔案到指定的目錄,比如上例中的D:\home\admin\share\data\helloworld目錄,否則就會看到如上一篇文章中所介紹的異常資訊。下圖展示了system.properties檔案提供的預設配置内容:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖二)system.properties内容

該檔案中定義了一些預設的全局參數,比如:應用程式的工作目錄路徑,伺服器的ID辨別,資料庫驅動配置等(開發架構支援MySQL的Master/Slave模式的配置,從上圖中就可以看到,我們配置了2個資料庫的URL參數,後續文章将對資料庫相關操作進行詳細的讨論)。當然你也可以添加自定義的參數,隻要符合Java屬性檔案的格式即可。接下去讓我們來看看全局配置參數加載器是如何工作的。請在Eclipse IDE中打開core-platform Bundle的bundle-context-osgi.xml檔案,如下圖所示:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖三)

開發架構提供了一個OSGi的服務定義,org.storevm.eosgi.properties.loader.SystemPropertiesLoader,該服務用于在Bundle啟動時加載system.properties屬性檔案。另外該服務還有一個“location”屬性,用于定義system.properties檔案的路徑(如果路徑中不提供檔案名,則使用預設的system.properties做為檔案名)。現在我們的全局參數已經全部被加載了,接下去我們将在程式中讀取這些全局參數。

請打開biz-share Bundle中的bundle-context-osgi.xml配置檔案,如下圖所示:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖四)

在該檔案中開發架構提供了全局配置參數加載器服務的引入配置,如下代碼:

<osgi:reference id="propertiesLoader" inter/>
           

該配置将core-platform Bundle中釋出的OSGi服務引入到biz-share Bundle中,這樣在biz-share Bundle中就可以使用這個OSGi服務了,請看bundle-context.xml配置檔案中的代碼,如下:

<!-- OSGi service annotation processor -->
<bean class="org.storevm.eosgi.core.annotation.OsgiServiceBeanPostProcessor" />
<bean class="org.storevm.eosgi.core.annotation.ServiceReferenceInjectionBeanPostProcessor" />
	
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" depends-on="propertiesLoader"/>
           

前2個配置是關于OSGi的annotation配置,下一章我們将對此進行詳細讨論,這裡我們暫時忽略它。第三個bean配置是我們經常用到的Spring的占位符替換的Bean。它依賴于我們的全局配置參數加載器服務的Bean。隻有當PropertiesLoader服務執行個體化之後PropertyPlaceholderConfigurer才會進行執行個體化,這樣,我們就可以在Spring XML中使用如下的配置了:

<bean id="masterDataSource" class="com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean">
	<property name="uniqueResourceName" value="${unique.resource.name.one}" />
	<property name="url" value="${jdbc.url.one}" />
	<property name="driverClassName" value="${jdbc.driverClassName}" />
	<property name="user" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
	<property name="poolSize" value="20" />
	<property name="borrowConnectionTimeout" value="60" />
	<property name="testQuery" value="select 1" />
</bean>
           

其中用${}表示的占位符就是配置在system.properties檔案中的屬性值。

這裡可能會有人提出疑問,PropertyPlaceholderConfigurer不是也提供了一個locations屬性值用于指定屬性配置檔案的路徑嗎?是的,但是這裡存在一個問題,PropertyPlaceholderConfigurer隻能讀取本Bundle中的*.properties檔案,如果其他Bundle要讀取相同的參數值,則需要在其他Bundle中重新再定義一個相同的*.properties檔案,這是由OSGi的Classloader隔離機制所導緻的,而且屬性檔案被打包到Bundle中,我們就失去了在運作時修改配置參數的靈活性,是以開發架構提供了一個單獨的OSGi服務用于加載全局的配置參數,這樣所有的Bundle隻要引入該OSGi服務就能讀取統一的配置參數了。以下代碼展示了如何在代碼中使用PropertiesLoader服務:

/**
 * 
 * @author Administrator
 * @version $Id: SystemPropertiesLoaderTest.java, v 0.1 2013-2-26 下午6:33:40 Administrator Exp $
 */
@Component
public class SystemPropertiesLoaderTest {
    private static final Logger LOGGER = Logger.getLogger(SystemPropertiesLoaderTest.class);

    /* 全局參數加載器 */
    private PropertiesLoader    propertiesLoader;

    /**
     * 讀取全局配置參數
     */
    @PostConstruct
    public void readProperties() {
        LogUtils.info(LOGGER, "讀取全局配置參數, share.data.path={0}",
            propertiesLoader.getProperty("share.data.path"));
    }

    /**
     * Setter method for property <tt>propertiesLoader</tt>.
     * 
     * @param propertiesLoader value to be assigned to property propertiesLoader
     */
    public void setPropertiesLoader(PropertiesLoader propertiesLoader) {
        this.propertiesLoader = propertiesLoader;
    }

}
           

從代碼中我們可以看到,在readProperties方法中我們讀取了“share.data.path”配置參數,從圖二中我們可以看到share.data.path的參數值。由于我們使用了annotation,是以無需再在spring的配置檔案中注冊bean。在我們的開發架構中,所有bean的配置均使用annotation完成。好了,我們可以啟動Eclipse中的OSGi運作時容器了,點選工具欄上的運作按鈕,并選擇“helloworld”,如下圖所示:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖五)

Console中會顯示大量的啟動日志資訊,如果運作正常,你将會看到如下圖所示的資訊,表示我們的測試成功了:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖六)

從上圖可知,程式讀取的“share.data.path”配置參數值和我們在system.properties檔案中配置的值完全一樣。目前我們使用的是開發架構提供的運作時環境來進行代碼測試,後續文章我們将介紹開發架構提供的另外一種內建測試方法。

接下來我們将在OSGi容器的運作過程中,修改system.properties中的參數值,然後看看,在biz-share Bundel中是否讀取到了修改之後的新值。

請在Console中輸入“ss”指令,然後會列出所有的Bundle,如下圖所示:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖七)

我們查找core-platform和biz-share Bundle的id值,如下圖所示(可能你的id與圖中的不一樣,以實際情況為準):

基于OSGi的企業級開發架構實踐——全局參數工具

(圖八)

從上圖可以看到,biz-share Bundle的id是116,而core-platform Bundle的id是118。我們修改system.properties檔案中的“share.data.path”配置項,如下圖:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖九)

我們在Console中的“osgi>”提示符下繼續輸入如下指令:

stop 118
           

該指令将停止core-platform Bunlde的運作,在Console中會顯示如下提示資訊(由于篇幅關系,這裡的提示資訊進行的删減):

osgi> stop 118
INFO [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext] - Unpublishing application context OSGi service for bundle Helloworld...
INFO [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext] - Closing OsgiBundleXmlApplicationContext(bundle=helloworld-core-plat...
INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Destroying singletons in org.springframework.beans.factory.support...
INFO [org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean] - Unregistered service [ServiceRegistrationWrapper for {org.storevm...
INFO [org.springframework.osgi.extender.internal.activator.ContextLoaderListener] - Application context succesfully closed (OsgiBundleXmlApplica...
           

接着我們輸入:

start 118
           

該指令将啟動core-platform Bundle(stop和start是常用的重新開機Bundle的指令),在Console中會顯示如下提示資訊(由于篇幅關系,這裡的提示資訊進行的删減):

INFO [org.springframework.osgi.extender.support.DefaultOsgiApplicationContextCreator] - Discovered configurations {osgibundle:/META-INF/spring/*.xml}...
INFO [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext] - Refreshing OsgiBundleXmlApplicationContext(bundle=helloworld-core-p...
INFO [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext] - Application Context service already unpublished

osgi> INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from URL [bundleentry://118.fwk18644877/META-...
INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from URL [bundleentry://118.fwk18644877/META-INF...
INFO [org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor] - No outstanding OSGi service ...
INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Pre-instantiating singletons in org.springframework.beans.factory.support...
INFO [org.storevm.eosgi.properties.loader.PropertiesLoader] - 讀取到全局配置檔案路徑, location=D:\home\admin\share\data\helloworld\system.properties
INFO [org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean] - Publishing service under classes [{org.storevm.eosgi.properties.loa...
INFO [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext] - Publishing application context as OSGi service with properties {org...
INFO [org.springframework.osgi.extender.internal.activator.ContextLoaderListener] - Application context successfully refreshed (OsgiBundleXmlApplicatio...
           

從中我們可以看到,system.properties被重新加載了。

接着我們重新開機biz-share Bundle,和之前重新開機core-platform Bundle一樣的方法,輸入如下兩個指令:

stop 116
start 116
           

Console中會顯示如下資訊:

基于OSGi的企業級開發架構實踐——全局參數工具

(圖十)

我們在不停止OSGi容器的情況下,修改了全局配置參數,并讓其他Bundle可以讀取到修改之後的配置參數,這展現了OSGi在動态性方面的特性。在實際運作中,我們隻需要重新開機core-platform Bundle就可以了,這裡是為了示範修改前和修改後的參數值差異,是以重新開機了biz-share Bundle。

下一章中,我們将介紹開發架構提供的一些OSGi Annotations,它們可以幫助我們提高注冊OSGi服務和使用OSGi的效率。