閱讀569 返回首頁    go 技術社區[雲棲]


對OSGi軟件構件化方法的設計理解

引言

       在對OSGi框架的設計過程中,我們遇到了有很多需要考慮和設計的問題。我們把中間件設計成OSGi框架,而OSGi也非常適合作為中間件基礎。關鍵的關鍵還是在於設計。在用傳統的方式進行設計時,就算不考慮太多關於接口、包隔離、模塊隔離、依賴關係都可以做一個能運行良好的係統,把功能加上能跑就好。但OSGi卻要強迫人們先拿出一個良好的設計再說,很多設計者缺乏這方麵的經驗,難受是自然的。

       除了不斷的嚐試和討論分析,我們還需要參考一些開源的項目設計。比如Apache的Amdatu項目,這個雲計算應用部署平台裏還集合了許多subprojects,有許多與我們類似的架構設計,而且他有個比較核心的類以及類的管理存儲機製,是關於multi-tenant(多租戶)的,這對我們來說也是一個有啟發性的思路。Amdatu集合了Apache的Felix框架,ACE,還運用到了Jclouds的Blobstore。目前我對這幾個項目的源碼和框架設計還解讀不熟,可能會在今後以博文的方式記錄下我的學習心得,到時候會把我們自己的項目也大體進行一個對比介紹。主要目的還是希望可以和從事OSGi框架設計的朋友們一起探討和發現。


基於OSGi的軟件構件化方法

1    軟件係統結構設計

基於分層思想提出了基於OSGi的構件化軟件係統總體框架,如圖 1所示係統大致分為以下四個層次。內核層,采用OSGi實現作為係統執行環境。基礎層,包括公共服務、共享資源和第三方JAR等。其中,公共服務Bundle提供上層構件實現中可能會用到的服務;共享資源Bundle提供軟件構件層各Bundle可能會用到的共享資源;第三方JAR Bundle是將原係統中JAR包統一管理,對外提供調用接口。軟件構件層,根據高內聚低耦合的原則,對原來的係統進行構件劃分,每個構件實現特定的功能,相互之間的功能相對獨立。根據實現和接口相分離的原則,每個構件都是由構件的具體實現即軟件構件Bundle和構件抽象出來的接口即構件API Bundle所構成。軟件構件Bundle調用其他的構件API Bundle和基礎層Bundle提供的功能,並且通過構件API Bundle對外提供調用接口。服務提供層,將軟件構件層各個Bundle提供的服務接口進行統一管理,以供上層應用開發時使用,便於通過調用底層功能和服務對係統進行擴展。

圖1 基於OSGi的構件化軟件係統框架圖

2    基於OSGi的軟件構件化方法

基於OSGi的軟件構件化方法分為兩個階段,第一階段為軟件構件化,第二階段為構件服務化,如圖 2所示。第一階段分析遺留軟件以確定構件劃分,這一步驟需要領域知識或者已有的文檔。根據分析結果,遺留軟件被劃分為若幹低耦合、高內聚的塊,它們隨後被封裝成各個構件。分析各構件之間的依賴,依照這些依賴抽取出構件的接口,使構件之間的依賴都變成以依賴相應接口的形式體現。第二階段是依照構件的接口構造各個構件提供的服務,目標是提供運行時動態性,服務的注冊、發現和綁定都交由底層框架實現,構件提供生命周期接口,供管理員啟動、停止和更新服務。

 

圖2 基於OSGi的軟件構件化方法

2.1       軟件構件化

(1)構件劃分

構件是可以獨立部署和卸載的單元,一個構件一般具有一項或幾項功能,因此構件化的軟件有較高的內聚性。同時,一個構件不需要關心其它構件的實現,它隻需要了解其它構件的接口和相應的約定,因此構件化的軟件是低耦合的。傳統的Java軟件係統缺乏有效的信息隱藏機製,各構件之間的依賴是隱式的,經常會出現多個構件共同實現一項功能或者一個構件包含多項功能。我們需要做一些調整,將共同實現一項功能的多個構件合並為一個構件,同時,一個實現多項功能的構件需要被劃分為多個構件,並且良好地定義相互之間的依賴關係。OSGi是一種麵向服務的體係結構,它不但提供了Package方式共享代碼和資源,而且提供了一個定義良好的服務層,支持麵向服務的編程。通過使用OSGi的服務層,可以實現係統的動態性,使係統具有較低的耦合性。基於OSGi平台的Bundle實現應該避免強依賴於其他Bundle的資源或服務,即程序不能認為其他Bundle中的資源或服務一定存在,這可以通過具體的編程實現來完成。

(2)接口抽取

為增加軟件的動態性,我們需要將接口與實現相分離。在傳統Java開發中,工廠設計模式和控製反轉被用來創建對象實例,調用和被調用的對象之間的關係是隱式的。在OSGi環境中,Bundle之間的依賴由延遲綁定機製實現,麵向接口的設計方法將接口與實現劃分到不同的Bundle中。OSGi在運行時才構成依賴,為每個Bundle提供獨立的類加載器,使得我們可以真正的做到麵向接口的開發。將接口和實現放在不同的Bundle中,並導出接口Bundle中所有的包,隱藏實現Bundle的包,可以強製用戶使用服務和接口進行調用。這樣分離後能夠容易地添加新的服務實現或者替換原有的服務實現,而這個過程中,接口Bundle都不需要有任何變化。將構件實現和提供的服務接口封裝在不同的Bundle中,在運行時由OSGi框架自動完成注入,可使係統具有較高的動態性。

(3) 依賴顯式聲明

大型軟件係統一般都會由若幹構件組成,這些構件合作以實現軟件的功能。若一個構件找不到它所要依賴的服務,則不能正常運行。在構件開發中,開發人員可能並未對記錄服務之間依賴的文檔引起足夠重視。依賴描述不足使構件的組合和演化變得困難,構件化的軟件需要顯式地聲明服務接口。另外,由於第三方構件更新頻繁,而一個構件一般需要依賴另一構件的某一特定版本,因此在聲明依賴時也需要聲明版本號。

2.2       構件服務化

(1) 服務注冊和服務發現

通過將服務計算的思想引入構件模型,鬆散耦合的構件可以在係統運行時加入或離開係統。構件被用來實現服務,一個構件依賴其它構件的一些服務,也向外提供一些服務。係統核心層支持服務的注冊、發現和綁定機製,核心層依照構件和服務的描述文件來管理係統中的構件和服務。我們定義服務單元來發布服務,服務單元由服務構件實現,暴露若幹服務接口,如圖 3(A)所示。同時,服務構件查找和綁定它所依賴的其它服務,如圖 3(B)所示。

 

圖3 服務機製實例圖

(2) 服務綁定和生命周期管理

通過注冊事件或使用構件化軟件管理工具,我們可以讓構件感知係統的動態變化。在軟件構造過程中,構件依賴服務接口,而不是服務的具體實現,這是通過將接口與實現相分而離達到的,接口與實現在調用服務時綁定。通過服務生命周期管理接口和延遲綁定機製,構件可以在係統運行時加入或離開係統,不需要重新啟動依賴它們的構件。因此,我們可以使用這種機製在運行時添加、刪除或更新構件。

3    關鍵問題分析與解決

我們結合在基於OSGi的軟件構件化實踐過程中遇到的困難,分析了需要處理的典型問題,給出了相應的解決方案。

(1)       類加載機製

Java的類加載器決定了應用程序的運行時邊界;如果將一個類加載器賦予一個構件,則它決定了這個構件的運行時邊界。類加載器分離是OSGi的一個重要特性,在OSGi框架中,每個Bundle都有自己的類加載器。每個Bundle有明確定義的邊界,一個Bundle對其它Bundle隱藏自己的包和類,名字衝突就可以避免。並且,Bundle可以動態地從係統中卸載掉,裝卸Bundle就不會對Java虛擬機產生汙染。通過類加載機製,Bundle之間可以共享資源和類,前提是要顯式聲明導入和導出。在傳統的Java應用程序中,代碼經常會使用線程的上下文類加載器來加載類,程序代碼也經常會將上下文類加載器設置為一個用戶自定義的類加載器對象,該對象通常以AppClassLoader為它的父類加載器,這樣上下文類加載器總能找到在該應用程序類路徑上的類。然而在OSGi環境下,已經沒有一個可以加載到類路徑上所有類的類加載器。在一些情況下,當一個線程的控製流從一個Bundle跳轉到另一個Bundle,該線程的上下文類加載器在新Bundle中變得幾乎沒有什麼用處,因為一個Bundle的類加載器不能加載其它Bundle中的類。因此,我們應使用Bundle的類加載器來加載本Bundle中的類或者從其它Bundle中導入的類。然而,不少第三方庫使用線程的上下文類加載器來查找資源,例如,在我們對Java EE應用服務器進行構件化的過程中,類javax.naming.InitialContext的代碼使用線程的上下文類加載器來查找jndi.properties文件。為解決此問題,當控製流進入這些第三方庫之前,我們需要將線程的上下文類加載器設置為合適的對象,根據這個庫決定需要加載哪些類。

(2)       資源共享

在傳統的Java項目中,程序各構件編譯後的類文件通常會處於同一個空間中,如同一個目錄或者同一個JAR包。代碼中用到的資源文件也可能位於這一相同的空間中,如果各構件都需要訪問某一個資源,大家都能夠在程序的默認的類路徑中找到這個資源。但是在OSGi環境中,各個構件被封裝到不同的Bundle中,每個Bundle都有獨立的類加載器和類路徑,每個Bundle隻能訪問自己的類路徑下的資源。如果需要訪問其它Bundle中的資源,則需要在Manifest.MF中使用Require-Bundle。如果一個資源被多個構件使用,則大量的Require-Bundle會減弱OSGi的構件化,使構件間存在較多依賴關係,使得係統難以維護。第三方JAR包同樣存在共享問題,傳統的Java項目都會使用一些外部的庫,這些庫往往以第三方JAR包的形式存在。在將軟件OSGi化過程中,如何處理這些JAR包往往會成為一個難題。因為可能幾個Bundle都需要訪問同一個JAR包,如果將這些JAR包放到所有需要它的Bundle的類路徑中,很大程度上會造成重複,並且更新JAR包過程較為繁瑣。為了減少重複,提高構件的可替代性,可以將第三方JAR包封裝成Bundle,導出將會被外界使用的包,這樣就能夠使得各Bundle都能夠使用第三方JAR包含的類。

(3)依賴分析

在OSGi環境中,若一個Bundle要訪問其它Bundle中的類,則需要通過MANIFEST.MF文件中的Import-Package頭或Require-Bundle頭顯式導入這些類。當遺留係統中的構件被封裝成Bundle後,這些Bundle之間的依賴需要用一些靜態分析工具來計算得到。但一些運行時依賴難以用靜態分析工具獲得,這些依賴需要人工的代碼檢查。例如由於Class.forName(stringVar)語句造成的類依賴就很難通過靜態分析得到。同時,遞歸依賴也是Java應用程序中的一個常見而且困難的問題,在傳統Java係統中,雖然係統被劃分為若幹構件,但這些構件由於共享類路徑而難以被分離開。構件間的隱式依賴可能會導致遞歸依賴。若Bundle依賴圖中有環,則無法找到一個有效的Bundle加載順序,這樣Bundle的加載過程就會失敗。為解決此問題,我們將接口與實現分離開,如圖 4所示,構件就可以找到它所依賴構件的接口,這樣就解決了遞歸依賴的問題,係統就可以加載成功。同時,若接口Bundle之間出現了遞歸依賴,則這樣的一些接口Bundle應合並為一個Bundle。

 

圖4 OSGi係統對循環依賴問題的處理




以上分析及圖示,希望能對您的設計有所啟發



最後更新:2017-04-02 06:52:12

  上一篇:go svn服務端的安裝與使用方式簡介(二)
  下一篇:go Java Generic Deep Copy