天天看點

基于OSGI的Cache元件的實作

Author : Wenchu.cenwc 岑文初 Date : 2007-3-13 Email : [email protected]  源起:        平台新架構一個core module作為Loader Module,然後再啟動系統級的Modules,最後是業務級的Modules。        OSGI中文意思是開放服務網關接口,是規範性的接口規範定義。而Eclipse 3.0以後的架構都是通過OSGI規範來實作的,可插拔性和靈活的接口有目共睹。在Eclipse中有一個基于OSGI R4實作的Equinox項目,它在OSGI R4架構實作中十分優越。是以平台決定使用OSGI R4規範來實作平台的整體架構。而Equinox就已經為平台搭建了由Core到System到Service的裝載的機制。   準備: 1. 因為是基于Equinox來進行開發,是以首先機器上要有Eclipse 3.2.1以上的版本,我用的是 3.2.1版本。 2. 到 http://download.eclipse.org/eclipse/equinox/ 去下載下傳一些Equinox已經實作的bundles,對于我們開發來說會用用處,由于後面我們主要涉及到了通過web來測試,是以需要 org.eclipse.equinox.http_1.0.1.R32x_v20060717.jar這個bundle,還有就是例如需要使用DS作為Service來釋出就需要下載下傳 org.eclipse.equinox.ds_1.0.0.v20060601a.jar反 正全部下載下傳沒有壞處。不過要注意一點的就是最好按照你的Eclipse的版本來下載下傳對應的bundles,這樣才不會出現版本不對導緻的問題。将下載下傳完的 jar都放到%EclipseHome%/plugins下面,然後重新啟動Eclipse就可以使用這些bundles了。(關于Bundles說明一 下,在OSGI中,把資源,類,配置檔案都放在一個檔案目錄下作為一個Bundle,它有它的核心描述文檔和單獨的Class Loader和其他Bundles交流隻是通過OSGI的服務查找或者接口導出導入,是以每一個Bundle就可以看作很獨立的子產品對外隻暴露接口,高内 聚,低外耦,OO的封裝在這裡得到了很好的展現)。   設計:        Cache子產品主要定義了兩個接口:Cache和CacheManager。Cache不用多說,大 家常用的接口,CacheManager是管理Cache的接口,系統内部或者業務子產品不能直接建立和擷取Cache接口,統一通過 CacheManager接口來維護系統内部的Cache,這樣的好處就是整個系統所有的Cache都可以統一管理和監控,對于系統将來的性能監控以及網 絡管理等外圍接入提供了統一的接口和植入點。  

基于OSGI的Cache元件的實作

圖 1 接口定義

       根據系統的需要實作了兩類不同的Cache和CacheManager,一組是實作了 JBossCache的接口,另外一組實作了TBStore接口。JBossCache是典型的TreeCache,這點和我們接口最初的設計很好的吻合 了,每一個系統的Cache作為TreeCache的節點,CacheManager就是整個Tree,JBossCache可以配置成為分布式和本地的 兩類Cache,分布式Cache十分強大,支援事務的三階段送出以及逾時政策等,是以系統的普通的Cache都是通過這部分來實作和運作。另外為了融合 其他系統的TBStore的内容,增加了對TBStore接口的實作。具體的結構圖如下。圖中還有一個外部測試Bundle,它對于系統内部的Cache 和CacheManager的引用都是通過查找OSGI FrameWork的Context中的service來擷取接口,它可以動态的切換和操作不同的CacheManager來獲得不同類型的Cache, 同時CacheManager在運作期可以動态的加載和解除安裝,外部引用可以立刻反應出其變化。  

基于OSGI的Cache元件的實作

圖 2 整體類圖 開發: 需要建立一個接口Bundle,兩個實作Bundle,以及一個測試Bundle。 首先建立接口Bundle:CacheBundle。 建立工程:

基于OSGI的Cache元件的實作

繼續

基于OSGI的Cache元件的實作

注意要選擇Target PlatForm,選擇标準的osgi架構,因為以後我們最後還是要部署在通用的osgi架構下,不依賴于任何第三方的架構内容。  

基于OSGI的Cache元件的實作

繼續,由于是接口Bundle是以不需要用到Bundle的Activator來激活 Bundle,是以在Plug-in Options那個第一個選項也不需要了。同時後面我們的實作Bundle裡面也不需要選,因為我們将會使用OSGI的R4新的特性,直接通過DS來申明 服務。 OK,finish。 然後就是要修改Bundle的最核心的總控檔案,在META-INF下面的 MANIFEST.MF檔案了,這個檔案是Bundle的描述檔案,整個Bundle的資源,類,ClassPath等都是通過它來描述的,是以這個檔案 的正常與否直接關系到Bundle的部署是否成功以及功能是否正常。不過Eclipse替我們提供了一個很友好的圖形界面來編輯這個檔案,減少了我們配置 出錯的可能性。 輕按兩下MANIFEST.MF,預設會進入Overview标簽頁,這個是一個總覽頁面,具體還是要選擇後面的不同的标簽來做配置。我們一個個來介紹: Dependencies 标簽頁:配 置需要依賴的Imported Packages和外部的plug-ins也就是我們說的Bundles。(注意:習慣中大家如果發現工程缺少了某一個外部的jar或者接口的時候都通過 選擇工程的屬性,然後在Libraries裡面加入jar,但是這裡千萬千萬不要這麼做,問題在開發過程中看不出來,等到了部署的時候由于此時的 ClassPath都通過MANIFEST.MF中的定義來尋找,因為通過工程Libraries加入的jar不會被寫入到MANIFEST.MF中,因 此就會出現問題,是以需要外部的包或者接口的時候,就在此頁中加入,它會自動修改工程的Libraries)。 Runtime 标簽頁:分 成左右兩部分,左邊是需要導出的包,一般就是我們需要暴露給外部的接口包,以供其他子產品或者外部引用實作,這裡面我們導出了 com.osgi.demo.cache接口包。右邊是Classpath,描述一下在Bundle釋出的時候的Classpath,一般我們預設建立工 程的時候将class的輸出目錄定為bin,選擇把bin加入到classpath,然後自己可以建立一個lib目錄在工程中,作為第三方lib的放置目 錄,然後就要在這邊選擇lib目錄也作為Classpath。 Build 标簽頁:最上面左邊右邊分别是選擇在運作期自動從源碼目錄編譯到目标目錄的兩個清單,是以我左邊選擇了bin作為目标class置放的目錄,右邊選擇src目錄,儲存一下。 然後為了在導出生成jar的時候會把配置檔案,資源檔案,第三方jar等内容一起導出,就需要在Binary Build裡面把需要到處的内容打上鈎。        最後的兩個标簽分别是MANIFEST.MF文本編輯頁面和Build文本編輯頁面,當你修改完上面的内容以後可以自己手動的修改這兩個檔案。        寫完配置檔案以後,隻需要按照我們通常的開發模式在src下面開始編寫java的内容了。   接着就是編寫兩個接口實作Bundle了,總體的流程和接口Bundles一緻,就把不太一樣的說明一下: 作為總控檔案的不同主要在imported Packages中需要導入接口定義的那個包com.osgi.demo.cache,還需要依賴一個 org.osgi.service.component的包,因為後面我們需要通過DS來申明和調用service,也需要通過ds的生命周期回調函數來 初始化和析構。需要建立一個DS的配置檔案目錄以及配置檔案,我這邊就在工程中建立了一個OSGI-INF目錄,然後再此目錄中建立了檔案 component.xml作為配置檔案,最後需要手動在MANIFEST.MF檔案中增加一句 Service-Component: OSGI-INF/component.xml 來指定配置檔案的路徑,記得要在Binary Build中也選中這個目錄,否則不會被打包到jar中,運作起來也就會出問題。具體的DS使用方法和配置方法參考一下OSGI R4的r4.cmpn.pdf。          最後測試類的開發,這邊是參照了OSGI實戰内的範例來通過WEB方式測試具體的業務邏輯。依賴的 package需要org.osgi.service.http以及org.osgi.service.component兩個eclipse網站下載下傳的 建構包和com.osgi.demo.cache包,同時還需要一個Plug-ins就是 org.eclipse.equionx.servlet.api。其他的配置都和上面的一樣。 在來看一下測試的Servlet的代碼片斷:   作為DS來管理Bundles内部類的情況下,提供了啟動和結束的兩個攔截方法:activate和deactivate。在測試類啟動的時候, context.getBundleContext();     Object[] services = context.locateServices( "CacheManager" );     cacheMap .clear();                for ( int i = 0 ;i < services. length ; i++)     {        Object node = services[i];        cacheMap .put(node.getClass().getName(), node);     } 通過 locateServices 來查找同一個接口服務所有的注冊對象,然後置入 Map 來切換調用。 DS 也提供了很友善的類似于 spring 的注入式的初始化(缺點就是注入的時候每次隻能夠擷取其中之一的接口注冊對象,根據注冊的先後,擷取靠後的對象),參看 DS 的配置使用。   在doGet中代碼片斷如下: if (cachetype != null ) {         if (cachetype.equals( "tbcache" ))         {             cacheManager = (CacheManager) cacheMap .get( "com.osgi.demo.cache.tbstoreImpl.TBStoreCacheManager" );         }                 if (cachetype.equals( "xcache" ))         {             cacheManager = (CacheManager) cacheMap .get( "com.osgi.demo.cache.xImpl.XCacheManager" );         }         }   if ( cacheManager == null ){         output.println( "No usable cacheManager service" );         output.close();         return ;     } if (operater.equals( "create" )) {     cacheManager .create(cacheName);     output.println( "create cache success" ); }                   if (operater.equals( "put" )) {     Cache cache= cacheManager .get(cacheName);                          if (cache != null )     {        cache.put(key, value);        output.println( "put cache success" );     }     else        output.println( "cache is null" );                      } ……       至此,開發部分基本結束,已經可以在 eclipse 裡面調試和運作了。調試和運作的話,首先,

基于OSGI的Cache元件的實作

選擇 Equinox OSGI Framework 這種類型,然後 new 一個。在選擇 plug-ins 中自己的幾個 Bundle 以及系統的,這邊自己的幾個不說了,說一下系統的幾個: ds,http,servlet,services 。系統自己預設會選中一個 osgi 的最基類 bundle 。 OK, 你可以自己設定斷點調試運作了。   部署:        當在Eclipse裡面測試完成以後,其實工作才作了70%,剩下的30%将會更加困難,因為你将脫離調試環境,進入指令行環境來測試和驗證各個Bundle是否正常。        部署前第一步就是需要導出各個Bundle為jar。        選中某個需要導出的工程,選擇Deployable plug-ins and framents

基于OSGI的Cache元件的實作

Next以後就隻要選擇自己需要導出的位置,不過注意的是,導出以後會自動增加一層plug-ins目錄,這個主要是在配置啟動腳本的時候需要注意的。 現在來建構一個釋出總目錄,建立一個檔案夾,将eclipse的plugins裡面的org.eclipse.osgi_3.2.1.R32x_v20060919.jar(版本不同後面的數字不同)拷貝到目錄中,并重新更名為equinox.jar。 然後建立一個configuration目錄建立一個config.ini作為配置檔案,範例如下: #Configuration File #Mon Jun 12 21:04:30 CST 2006 osgi.noShutdown=true osgi.bundles=reference/:file/:bundles/[email protected],/ reference/:file/:bundles/[email protected],/ reference/:file/:bundles/[email protected],/ reference/:file/:bundles/[email protected],/ reference/:file/:bundles/plugins/[email protected],/ reference/:file/:bundles/plugins/[email protected],/ reference/:file/:bundles/plugins/[email protected],/ reference/:file/:bundles/plugins/[email protected] osgi.bundles.defaultStartLevel=4   建立一個bundles用來放bundles,系統級的現在一共用了4個(http,servlet,ds,services),再将剛才導出的plug-ins目錄拷貝到bundles下面(其實後面就在導出的時候指定導出位置為bundles就可以了)。 建立一個批處理運作檔案run.bat.,内容如下(不多解釋,就是普通的jar運作): java -Dorg.osgi.service.http.port=8080 -jar equinox.jar -console   OK,可以Run了。   測試        功能性測試在Eclipse開發中已經基本走過了,剩下的就是為了驗證一下bundle的動态載入和解除安裝。        預設的配置檔案中,程式起來以後每個bundle都回變成active。        指令行輸入:ss可以檢視bundle的狀态。Bundles可以更清楚地看到每一個bundles的狀态以及注冊的service和引用的service。        這裡首先輸入ss,可以看到螢幕輸出:   Framework is launched.   id      State       Bundle 0       ACTIVE      system.bundle_3.2.0.v20060510 1       ACTIVE      org.eclipse.osgi.services_3.1.100.v20060511 2       ACTIVE      org.eclipse.equinox.ds_1.0.0.v20060601a 3       ACTIVE      org.eclipse.equinox.http_1.0.0.v20060601a 4       ACTIVE      org.eclipse.equinox.servlet.api_1.0.0.v20060601 5       ACTIVE      CacheManagerBundle_1.0.0 7       ACTIVE      XCacheBundle_1.0.0 8       ACTIVE      TBStoreCacheBundle_1.0.0 9       ACTIVE      CacheTestBundle_1.0.0   如果使用注入的方式,那麼在CacheTestBundle裡面能夠擷取到的CacheManager接口隻能是TBStoreCacheManager的實作,這邊我們使用了不是注入方式,是在Test啟動的時候,載入全部的可用接口注冊服務,以此沒有此問題。 當使用stop 8以後,發現TBStoreCacheBundle目前的對象被釋放,但是如果去調用CacheTest的TBStoreManager的實作,依然會 new一個新的對象來運作,也就是說就算TBStoreCacheBundle被Stop,處于非Activie的時候CacheTest還是能夠繼續使 用的。 此時我嘗試了修改了TBStoreManager中的邏輯,加了一句列印語句,然後重 新打包,這時再把TBStoreCacheBundle uninstall了,然後再install此Bundle并且start,對于CacheTest一點沒有什麼影響,接着當我stop了 CacheTest以後,然後再start,此時可以發現,新的bundle實作那句輸出啟效了。 是以由此可見,如果我們要在運作期替換一些業務實作十分容易,對于業務子產品基本是透明的,而且不需要重起伺服器,這種無間斷的運作期替換對于網站來說十分具有吸引力。來看看應用場景。 例如: 場景一:A業務子產品使用了系統所提供的K接口,K接口此時有多個實作暫時叫做 K1Bundle,K2Bundle,K3Bundle。此時如果A是通過DS的注入來擷取接口,那麼如果按照 K1Bundle,K2Bundle,K3Bundle,A的順序載入,那麼A應該預設的擷取到了K3Bundle作為業務邏輯處理,如果此時stop K3Bundle,再重新啟動A子產品,那麼A将會使用K2Bundle,不知不覺中就切換了實作,真正的面向接口對外開放。        場景二:A業務子產品使用了系統所提供的K接口,K接口此時由K1Bundle實作,那麼載入以後正常運作,當K1Bundle修改以後,重新打包,并且重新裝載并啟動,然後再重起A子產品,那麼K1Bundle的新邏輯将會啟用。   Tips 1. 接口定義 包名千萬注意不要和實作包名同名,例如接口包為com.osgi.demo.cache,開始的時候我把XBundleCache中的實作類直接放入到了 com.osgi.demo.cache下,結果由于這個Bundle有引用接口包,那麼兩個包沖突了, 結果半天沒有出來正常的運作結果。是以多加一層com.osgi.demo.cache.xImpl或者幹脆就改名。 2. DS的載入順序很重要。如果要調用某個ds,那麼這個ds要優先于調用這load,同時記得系統的services,ds要優先load。 3. DS本來有自己的生命周期回調函數,定義為 protected void activate(ComponentContext context), 但是我自己新增以後怎麼跟蹤都沒有進入回調函數,找了半天才發現,原來用了eclipse的add import,誤把 com.sun.jndi.toolkit.ctx.ComponentContext導入了,由于在plug中沒有add in org.osgi.service.component這個包,是以就算寫了函數申明還是沒有起效,命苦。 4. 如果用外部啟動bundles的話,常常提示什麼已經初始化過了的錯誤,那麼去configuation的目錄中删除沒用的那些零時目錄就可以了。 5. 千萬千萬 記得這一點,我們習慣當需要引用外部的jar的時候通過project的屬性也裡面增加project的lib,得卻立刻紅色的錯誤消失了,但是帶來的苦 果就是當你釋出jar的時候由于沒有把核心配置檔案中的classpath修改好,導緻就算你把那些jar打入你的bundle裡面還是會出現找不到類。 6. 通過DS 來裝載最大的缺點就是如果一個component轉載失敗(可能由于某個類沒有找到),但是它還是提示你整個bundle是active的,直到運作期的 時候發現拿到了一個錯誤的service,報了一堆錯誤,或者本來想通過注入來植入一個service的,結果因為這個service初始化失敗,毫無聲 息的繼續正常使用,直到用到了這個service來執行流程。是以需要學會使用指令行的bundles檢視各個bundle的加載狀态以及bundles 的Service的載入狀态。同時要去分析在configuration目錄下面的日志檔案。 7. 要學會解 開jar來看裡面的東西,學會分析裡面的各個配置檔案。因為初學的時候對于eclipse的打包方式不是很清楚,是以很多問題其實是打包出錯,例如缺少 lib,配置等等,是以需要通過解壓工具解開jar看看裡面的配置情況以及目錄結構情況是否符合你的核心配置檔案的描述。

繼續閱讀