實現 WebSphere Application Server 上應用程式對 OSGi 的支援

CloudSpace發表於2009-12-23

轉自:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/0912_wangzq_wasosgi/index.html

引言

為了解決現實工程中遇到的版本衝突問題,我研究了 OSGi 技術。為此,我在網上搜尋了很多的有關於 OSGi 的文章,但是最後發現很少有人能清楚的闡述 OSGi 的由來和 OSGi 最本質的特性,直到我發現 Neil Bartlett 的 OSGi In Practice。它讓理解了 OSGi 的本質,從而清楚的明白:解決 Java 工程中的版本衝突問題,OSGi 是最好的選擇。這是由 OSGi 的本質決定的,這也是為什麼大的 Java 軟體產品紛紛開始採用 OSGi 框架的原因。Java 有天生的缺陷,而 OSGi 彌補了它。然後我又花了大量的時間來研究 WebSphere Application Server 是如何支援 OSGi 的,最後才能成功的使我的應用支援 OSGi,從而解決了版本衝突問題。但這一整個過程耗費了我大量的時間和精力,為了能讓遇到同樣問題的人少走彎路,我寫了這篇文章,希望能夠有所幫助。

OSGi 的由來

隨著科技和需求的發展和變化,現在的軟體變得越來越龐大。這樣,隨之而來的最大挑戰就是軟體在設計上的越來越複雜和維護上的越來越困難。為了解決這個問題,軟體架構師將軟體切分成比較小的並且易於理解的多個模組。那麼軟體模組化會給我們帶來什麼樣的好處呢?

  • 拆分人力:將軟體模組化後,我們就可以分配獨立的團隊去處理獨立的模組,從而將人力拆分開來。這樣既便於管理,又會降低整個軟體的設計的複雜性。因為每個獨立的團隊可以專心去設計和實現其模組,而不用通盤考慮整個軟體的複雜性。
  • 抽象化:將軟體模組化後,我們將整個軟體抽象化成多層、多 模組的一個整合。這樣使整個軟體易於理解,便於管理。
  • 重用:將軟體模組化後,每個模組有其獨立的功能和封裝。這樣這個模組就可以在多處(甚至是將來其他的軟體中)重用,從而節省人力。
  • 易於維護:將軟體模組化後,當軟體出現問題後,我們可以容易地定位問題出在那個模組,而每個模組又相對較小和易於理解,從而降低了軟體維護的難度。

基於上述的 4 個優點,在當前的軟體設計中,軟體模組化是軟體架構師的主流思想。為了實現軟體模組化,應運而生的就是物件導向的高階程式語言,Java 是其中的典型代表。Java 用其獨有的 Jar 格式檔案去包裝 Java 類和其他的資原始檔,從而可以將軟體元件封裝成獨立的 Jar 檔案。這些 Jar 檔案可以相互依賴並共同完成同一個工作,從而實現了軟體的模組化。但是 Java 卻不能真正的帶給我們軟體模組化的那 4 個優點,為什麼呢?為了解釋這個問題,我們需要知道模組的定義。

什麼是一個模組?

一個模組應該有以下 3 個特性:

  • 自包含:一個模組應該是一個業務邏輯的整體。它應該可以作為一個獨立的整體被移動、安裝和解除安裝。模組不是一個原子體,它可以包含多個更小的部分,但這些部分不能獨立存在。
  • 高內聚:一個模組不應該做很多不相關的事情,它應該專注於 1 個業務邏輯的目標並盡全力實現這個目標。
  • 低耦合:一個模組不應該關注其他模組的內部實現,鬆散的聯絡允許我們去更改某個特定的模組,而不會影響到其他的模組。

而 Java 語言的 Jar 檔案並不能完美的實現一個模組這 3 個特性,它主要會遇到以下的 3 個問題:

  1. 針對一個 Jar 檔案,沒有對應的 Java 執行時的概念。Jar 檔案只有在開發和部署的時候有意義,而在 JVM 中,所有的 Jar 檔案中的內容被簡單地聯絡在一起作為一個單獨的全域性的列表,這就是所謂的“Classpath”。這種類載入模式,使 Jar 檔案在執行時是不可見的。
  2. Jar 檔案沒有標準的後設資料資訊去指明該 Jar 檔案所需要的外部依賴檔案列表,這樣我們就不能清楚的知道,該 Jar 檔案需要和其他的那些 Jar 檔案一起工作。另外,現在的 Jar 檔案沒有版本資訊,這樣,同一 Jar 檔案的多個版本就不能同時被載入。
  3. Java 沒有機制在不同的 Jar 檔案中隱藏資訊。

這 3 個問題,使 Jar 檔案在模組的“自包含”和“低耦合”這兩個特性上做的不好,從而使 Java 在模組的“拆分人力”和“易於維護”這兩個優點上沒有好的表現,而更嚴重的是第 2 個問題,這使 Java 應用軟體存在難以處理的版本衝突問題。

Java 語言為了安全的考慮,它的類載入器(注意不是類載入)是多層立體的並且對其類載入採用了父委託機制。並且,當同一個類載入器載入類檔案時,JVM 會載入最先發現類。所以當同一個類的新舊兩個版本被分別封裝在兩個不同的 jar 檔案中(比如:ClassVersion2.jar 和 ClassVersion1.jar),而這兩個 jar 檔案又都在 JVM 的類載入路徑裡的話,最先被載入的 Jar 檔案中的類才會被使用到。而現在的 Java 應用軟體因為長時間的更新維護,同一個模組的多個版本共存的情況比比皆是,這就會產生版本衝突問題。比如說,在 Java 應用軟體中存在一個模組 ThirdComponent,這個模組依賴 ClassVersion 模組而且必須同老版本的 ClassVersion 模組(ClassVersion1.jar)一起工作,當新 ClassVersion 模組(ClassVersion2.jar)被升級到軟體中後,ThirdComponent 模組很有可能就不會工作,因為類載入器很有可能會先載入的是 ClassVersion2.jar,而不是 ClassVersion1.jar。其實產生這個問題的根本原因是,Java 的 Jar 檔案在自包含上做的不好。Jar 檔案只是在軟體的封裝上起到了作用,而在 JVM 執行時中,Java 只關心類而忽略了 Jar,從而使 Java 的類載入變成了平面的線性的而非立體的網狀的。

為了解決 Java 在模組化中存在的問題,OSGi 模組系統出現了。OSGi 是基於 Java 之上開發的,它提供了一種建立模組化的 Java 應用程式的方法並定義了這些模組在執行時中如何相互互動。

OSGi 最本質的特性

OSGi 是一個由大概 40 個公司組成的聯盟來共同定義的一個標準。依照這個標準,目前,有 4 種獨立實現了的 OSGi 框架,他們分別是:

  • Equinox: 這個 OSGi 框架是目前應用最廣泛的 OSGi 框架。它是由 IBM 開發的,目前已經被應用到 Eclipse,Lotus Notes,IBM WebSphere Application Server 等等。Equinox 實現了版本為 4.1 的 OSGi 規範。
  • Felix:這個 OSGi 框架實現了版本 4.x 的 OSGi 規範,它是由 Apache 開發和維護的。
  • Knopflerfish:這是一個流行併成熟的實現了版本 3 和 4.1 的 OSGi 規範的 OSGi 框架。它是由 Makewave AB 開發和維護的。
  • Concierge:這個 OSGi 框架實現了版本 3 的 OSGi 規範。

關於 OSGi 這個名字,2 個最常被問到的問題是 OSGi 代表什麼?為什麼 i 是小寫的?

關於這兩個問題,權威性的回答是:官方上 OSGi 不代表任何東西,然而,通常上說 OSGi 代表“Open Service Gateway initiative. 而小寫字母“i”來自單詞“initiative”。

至於 OSGi 的中心思想,它非常地簡單。傳統 Java 軟體問題的根源就是全域性的扁平的類載入路徑(Classpath),所以 OSGi 採用了一種完全不同的類載入機制,那就是每個模組都有其獨立的類載入路徑。這幾乎解決了傳統 Java 在模組化中遇到的所有問題,然而一個新的問題又產生了,軟體中的模組是要在一起工作的,這就意味著不同的模組之間存在類共享(不然的話,一個模組如何能夠呼叫到另外一個模組呢),如果每個模組有一個類載入路徑,模組間的類共享如何解決?為了解決這個問題,OSGi 定義了一個特殊的並完善的類共享機制。OSGi 將會採用顯示的匯入和匯出機制來控制模組間的類共享。

在 OSGi 中,模組被起了另外一個名字,叫做 bundle。實際上 OSGi 的 bundle 就是一個 Java 的 Jar 檔案。OSGi 並沒有定義一個新的標準去封裝 Java 類和其他的資原始檔,標準 Jar 檔案可以很好地工作在 Java 應用軟體中。在 OSGi 體系裡,只是一些新的後設資料資訊被加入到 Jar 檔案中,這些後設資料資訊使的 Jar 檔案變成一個 OSGi 體系中的 bundle。那麼什麼樣的後設資料資訊被加入進來了呢?

  1. Bundle 的名字。OSGi 提供了一個“symbolic”名字作為這個 bundle 的唯一識別符號。
  2. Bundle 的版本資訊。
  3. Import 和 export 列表。從這個列表,我們可以清楚地知道這個 OSGi bundle 需要匯入和匯出那些包的類。匯入的包是本 bundle 需要用到的外部資源,匯出的包是其他 bundle 可以用的本 bundle 中的資源。
  4. Bundle 需要執行的最小的 Java 版本。這個資訊是可選的。
  5. 種類繁雜的人類可讀的其他資訊,比如說:本 bundle 的提供者,版權陳述,聯絡地址等等。

這些後設資料資訊被放到 Jar 檔案的 MANIFEST.MF 檔案中,而這個檔案是每個標準 Jar 檔案的一部分。用一個標準的 Jar 檔案作為 OSGi bundle 的一個好處是 bundle 可以被用在 Jar 檔案可以出現的任何一個地方,因為 bundle 就是一個純粹的 Jar 檔案。當一個 bundle 用在 OSGi 的執行時之外的時候,這些額外多出來的後設資料資訊會被 Java 執行時簡單地忽略掉,所以說,bundle 是向前相容的。那麼除了這個,OSGi 的 bundle 還給我們帶來了什麼樣的好處呢?

為每一個 bundle 提供一個類載入路徑意味著什麼呢?簡單地說我們為每一個 bundle 提供了一個類載入器,這個類載入器能夠看到這個 bundle 檔案裡的類和其他資原始檔。但是為了達到多個 bundle 共同工作的目的,在 OSGi 的類載入器之間,類載入請求可以從一個 bundle 的類載入器被委託到另外一個 bundle 的類載入器。回想一下在標準 Java 和 J2EE 中,類載入器是一個樹形結構的,類載入請求總是被向上委託給每一個類載入器的父親。這中類載入機制不允許在水平的樹節點之間進行類載入委託。為了讓一個類庫可以被類載入器樹的多個樹枝共同所見,我們就需要將這個類庫推到這些樹枝共同的祖先節點。這樣這個版本的類庫就會被這些樹枝上的所有節點所見,而不管是否這些節點都想看到這個版本的類庫。圖 1 是一個典型的 J2EE 類載入器層次結構,它展示了為什麼類庫會不斷推高的原因。


圖 1. 典型的 J2EE 類載入器層次結構
圖 1. 典型的 J2EE 類載入器層次結構


而樹型結構並不是我們需要的,我們真正需要的是網狀結構。兩個元件之間的依賴關係不是簡單的上下級的關係,而應該是一種提供者和使用者的網路關係。類的載入請求被從一個 bundle 的類載入器委託到另外一個 bundle 的類載入器,而這種委託是基於 bundle 之間的這種網狀的依賴關係。圖 2 給了我們一個 OSGi 中 bundle 之間的網狀的依賴關係的例子。


圖 2. OSGi 中 bundle 之間的網狀的依賴關係
圖 2. OSGi 中 bundle 之間的網狀的依賴關係


在 OSGi 中,bundle 之間的依賴關係是通過顯示的 import 和 export 類包列表來決定的。比如說,在圖 2 的 bundle B 中包含一個類包,名字為 com.ibm.bundle.b.somePackage.。Bundle b 就可以選擇在它的 MANIFEST.MF 檔案去 export 這個類包。而 bundle A 也可以選擇去在它的 MANIFEST.MF 檔案中 import 類包 com.ibm.bundle.b.somePackage。然後在 OSGi 執行時中,OSGi 框架將負責匹配不同 bundle 的 import 和 export 列表。而這個匹配過程被稱為 OSGi 的解決過程(resolution process)。OSGi 的解決過程是相當複雜的,但是這個過程是被 OSGi 框架實現了的,不需要每個 bundle 自己來關心它。每個 bundle 只需要寫一些很簡單的 import 和 export 宣告語句在各自的 MANIFEST.MF 檔案裡。一旦 OSGi 框架匹配了 bundle A 的 import 列表裡的類包 com.ibm.bundle.b.somePackage 與 bundle B 的 export 列表裡的類包 com.ibm.bundle.b.somePackage,那麼這兩個 bundle 就會被連線在一起,而這就意味著當 bundle A 需要載入任何類包 com.ibm.bundle.b.somePackage 裡的類時,這個類載入請求就會被委託給 bundle B 的類載入器,而 bundle B 的類載入器將會載入這個類,並將類例項傳給 bundle A。而因為 bundle A 依賴 bundle B 和 bundle C, 如果 bundle A import 列表裡的所有類包都能在 bundle B 和 bundle C 的 export 列表裡發現,那麼 bundle A 將會被稱為解決成功,它會進入解決了狀態(resolved)。如果 bundle A 的 import 列表裡需要的某些類包沒有在 bundle B 和 bundle C 的 export 列表裡發現,那麼 bundle A 的解決就沒有成功,bundle A 將不能被使用和啟動。由此可見,OSGi 中,bundle 之間的依賴關係是通過顯示的 import 和 export 列表來決定的。

  1. 隱藏了 bundle 中的資訊。
    因為在 OSGi 中,bundle 之間的依賴關係是通過顯示的 import 和 export 列表來決定的,所以我們沒有必要 export 一個 bundle 中的所有的類包,進而能起到隱藏 bundle 中資訊的作用。在 OSGi 中,只有那些被顯示的 export 出來的類包才能被其他的 bundle import。
  2. 增加了版本控制並允許多版本並存。
    OSGi 不僅僅使 bundle 之間通過類包名相互依賴,它還可以為類包加入版本資訊。這是我們能夠應付 bundle 的版本變化問題。Export 的類包也可以攜帶一個版本資訊,而 import 卻可以引用一個版本範圍內的所有類包,這讓我們的 bundle 可以依賴一個版本範圍內的所有類包。於是說,在 OSGi 中,同一 bundle 的多個版本就可以同時存在。在本文的第三章,我將詳細介紹,OSGi 是如何允許同一個 bundle 的多個版本並行存在,進而客服了 Java 應用軟體中元件的版本衝突問題

    解釋 OSGi 是如果克服版本衝突問題的

    OSGi 之所以能夠解決 Java 應用軟體中元件的版本衝突問題,原因就是 OSGi 的網狀類載入器和 OSGi bundle 的版本資訊控制。為什麼這麼說呢?

    1. OSGi 的網狀類載入器架構使每個 OSGi bundle 都擁有一個獨立的類載入器,而 bundle 只是一個標準 Jar 檔案。這樣,對同一個 bundle 的不同版本,我們就可以建立多個不同的 Jar 檔案。這些 Jar 檔案的實際內容可以完全一樣,而只是檔名不同(甚至檔名都可以相同,因為在 OSGi 框架中,bundle 名和版本的組合才是唯一識別符號)。因此這些 Jar 檔案,在 OSGi 框架看來,是不同的 bundle,於是同一個元件的不同版本可以被同時載入到 JVM 中,這就解決了 Java 應用軟體中同一元件不同版本的並存問題,接下來只要解決版本辨識問題,那麼 Java 應用軟體中元件的版本衝突問題就會被客服掉了。
    2. OSGi 新增了版本控制資訊來區分同一個 bundle 的不同版本,而且在 OSGi 框架中 bundle 名和 bundle 版本的組合才是這個 bundle 的唯一識別符號,於是通過 bundle 的版本控制,同一個 bundle 的不同版本就可以得到區分,而 bundle 直接的依賴關係也可以通過版本來加以限制,從而就能完美的解決 Java 應用軟體中元件的版本衝突問題了。

    那麼 OSGi 是如何進行版本控制的呢?

    OSGi 通過在 MANIFEST.MF 檔案中新增 Bundle-Version 屬性來個為每一個 bundle 新增一個版本資訊,而且這個版本資訊必須嚴格遵循:3 數字段 +1 字元段的格式,6.2.0.beta_3 是一個典型有效的 bundle 版本資訊。這前面的 3 個數字段就是大家都知道的主版本,小版本和微版本,而那個最後的字母段則是校正段。當前面的 3 個任意一個數字段沒有值時,OSGi 將會隱式地將 0 付給這個欄位,所以版本 1 是和 1.0、1.0.0 相同的。而如果沒給 bundle 指定任意一個版本,那麼 0.0.0 將被認為是這個 bundle 的版本資訊。

    另外 OSGi 中,版本的比較是採用從前到後的比較方式。如果在版本比較時,第一個數字段就不同,那麼後面的 3 個欄位就不用比較了,因為 OSGi 的前一個版本段是後面所有欄位值的總和,所以大版本就不相同的時候,後面的小版本就不需要比較了,比如說:2.0.0 是大於 1.999.999。而如果兩個 bundle 的版本資訊,在前面的 3 個數字段都相同的時候,OSGi 就會對最後的字母段進行比較。而最後的字母段可以包含大寫或小寫的字母 A 到 Z、數字、連線線和下劃線,所以它的比較比較複雜。OSGi 採用了標準 Java String 類的 compareTo() 方法的演算法來進行比較,而標準 Java 的 String 類的 compareTo() 方法會對校正段的每一個字母按順序進行比較,直到出現差異。另外如果字母相同,那麼短的那個校正段的值將被認為小於長的校正段,beta_01 將會比 beta_010 小。

    最後需要提的是 OSGi 不但可以為 bundle 指定一個版本資訊,還可以為每一個類包指定一個版本資訊,即 bundle 的版本控制是可以做到類包級別的(而且這是推薦的 OSGi 版本控制方式)。當 bundle 在 export 類包時,使用者可以為每個類包指定一個版本資訊。而當 bundle 需要 import 某特定版本的類包時,使用者除了可以指定一個特定的版本資訊外,還可以指定一個版本資訊範圍。而這個範圍可以用方括號“【”和圓括號“(”來作為邊界,方括號“【”表示邊界值也在範圍之內,而圓括號“(”則相反 . 比如說【 1.0.0,2.0.0)表示從版本 1.0.0 開始到 2.0.0 之間的所有的小版本,2.0.0 不在這個範圍只內。下面是一些進一步的範圍列表的例子,在下面的表 1 中 x 代表有效的範圍列表:


    表 1. 版本範圍舉例
    樣例 版本範圍
    [1.2.3,4.5.6) 1.2.3<=x<4.5.6
    [1.2.3,4.5.6] 1.2.3<=x<=4.5.6
    (1.2.3,4.5.6) 1.2.3
    (1.2.3,4.5.6] 1.2.3
    [1.2.3,1.2.3] 1.2.3
    1.2.3 1.2.3<=x
    0.0.0<=x

    解釋 WAS 在引入 OSGi 之後的類載入機制

    OSGi 是如此優秀的一個框架,因此很多 Java 應用軟體開始採用它,WebSphere Application Server(WAS)作為 IBM 最中要的 J2EE 伺服器從版本 6.1 開始採用 OSGi 框架,因此當你的應用是基於 WAS 的並遇到了版本衝突問題,你就可以將你的應用轉換成 OSGi 的 bundle,從而解決版本衝突問題。在將你的 WAS 的應用轉變成 OSGi 的 bundle 之前,我們需要了解 WAS 是如何支援 OSGi 的,進而採取相應的行動。


    圖 3. WAS 從 6.1 以後的類載入器層次結構
    圖 3. WAS 從 6.1 以後的類載入器層次結構

    圖 3 是 WAS 從版本 6.1 以後的類載入器層次結構,從此圖,我們可以知道 WAS 並不是完全採用 OSGi 框架(可能是出於向前相容等因素考慮),IBM 只是將 WAS 的一部分變成了 OSGi 框架的類載入模式,而其他的部分繼續延續了以前版本的類載入器層 次結構。而 WAS 中 OSGi 的那部分類載入器,以閘道器的形式與 WAS 的擴充套件類載入器連線在一起。

    接下來,大家可能奇怪,WAS 原有的類載入器是如何同 WAS 中的 OSGi 部分的類載入器一起工作的呢? WAS 將如何進行類載入呢?

    為了解釋這個問題,我們首先要清楚,WAS 中的那一部分採用了 OSGi 的框架? WAS 將其常用的重要的外掛部分做成了 OSGi,而所有的這些外掛被放置在 WAS 的 plugins 目錄中,因此 plugins 目錄下的所有的外掛是以 OSGi bundle 的形式被載入到 WAS 中來的。接下來,我將解釋從版本 6.1 以後 WAS 將如何進行類載入。從圖 3,我們可以得知,WAS 的 OSGi 框架是與 Ext 類載入器連線在一起的,這就是說 WAS 的 OSGi 框架在 WAS 整體的類載入器中處於一個很高的層次。而 Java 的類載入是採用父委託機制的,這就使普通的 WAS 上的應用程式會一層層的向上請求載入類,這樣當某個特定的類在 WAS 的 OSGi 部分中被發現,那麼 WAS 的類載入委託就會進入到 WAS 的 OSGi 執行時中。這樣當這個類載入以後,而它又需要載入其他類時,這之後的類載入委託請求就會在 WAS 的 OSGi 執行時中相互傳遞。

    由此可見,如果我們需要將自己的 WAS 上的應用程式轉變成支援 OSGi,我們則需要將這個應用程式的模組轉變成 OSGi bundle,然後將其放置在 WAS 的 plugins 目錄下。

    介紹一個具有版本衝突問題的 WAS 上的樣例程式

    在本章,我會模擬一個 WAS 上版本衝突的問題,讓大家能清楚地看到這種版本衝突問題是如何出現的。然後在第六章,我會告訴大家如何將這個樣例程式的某些元件轉變成 bundle,從而解決了版本衝突問題。

    為了說明問題,我構建如下一個郵件系統,這個系統可以閱讀各種各樣型別的郵件。因為郵件的型別可以存在很多種,而且每一種的郵件可以使用獨特的瀏覽方式,為了設計和維護的簡單,我們將它拆分成多個模組。我們將各種郵件型別共用的介面和基礎類拆分成一個模組,而將每種不同的郵件型別拆分成各自獨立的模組。而為了便於描述將來會出現的版本衝突問題,我們只是用兩種簡單的硬編碼的郵件型別(FixedMailbox 和 XMLMailbox)來說明這個系統的整體結構,請看圖 4。


    圖 4. 郵件系統結構圖
    圖 4. 郵件系統結構圖

    這個結構圖能夠清晰得告訴我們,整個系統的通用介面和基礎類被封裝在 MailboxAPI 模組中,而每個具體的郵件型別將分別是一個模組(FixedMailbox 模組和 XMLMailbox 模組),這些模組將實現這些通用介面和每種郵件型別各自的業務邏輯,然後將郵件展示給使用者瀏覽。所以在這個圖中將最少有 3 個模組,他們會由不同的團隊來分別負責。當整個系統開發完畢之後,各個團隊需要將各自的元件釋出到執行環境當中。因為 MailboxAPI 模組(MailboxAPI.jar)是被所有的其他元件所依賴的,為了將來維護和釋出的方便,一般 MailboxAPI.jar 會被放置到 WAS 的 lib 目錄下,這樣,其他的每一個元件都能輕鬆的找到它,而 MailboxAPI 模組團隊也可以輕鬆地釋出和維護 MailboxAPI,因為釋出位置的唯一,MailboxAPI 模組的每一次釋出和維護將不需要通知到所有其他的團隊。而其他的模組,因為包含業務邏輯和展示層,他們一般都是一個 ear 檔案,所以他們的釋出位置明顯是與 MailboxAPI.jar 不同的。圖 5 和圖 6 展示給我們 FixedMailbox 和 XMLMailbox 正常執行後的樣子。


    圖 5. FixedMailbox 的執行情況
    圖 5. FixedMailbox 的執行情況

    圖 6. XMLMailbox 的執行情況
    圖 6. XMLMailbox 的執行情況

    為了方便大家的學習,我將已經開發好的這 3 個模組以附件的形式附在本文上,它們在壓縮檔案 Version1.zip 裡。你只需要解壓縮它,然後將 MailboxAPI.jar 放置在 WAS 的 lib 目錄下,然後啟動你的 WAS 系統,接著你需要安裝 FixedMailboxReaderEAR.ear 和 XMLMailboxReaderEAR.ear(他們分別代表 FixedMailbox 模組和 XMLMailbox 模組)。在 Version1.zip 你還會發現 FixedMailbox.jar 和 XMLMailbox.jar,它們分別是模組 FixedMailbox 和 XMLMailbox 的業務邏輯,而 FixedMailboxReaderEAR.ear 和 XMLMailboxReaderEAR.ear 是這兩個模組的最後封裝。

    注意:附件裡的每一個檔案裡,都含有原始碼,以方便感興趣的讀者深入研究。

    在整個郵件系統執行一段時間後,某些使用者較多的個別郵件模組就可能面臨著升級。因為客戶多,需求就多,而使用者的需求又是千奇百怪的,必然會出現某個特定的需求波及到了基礎模組 MailboxAPI。這個時候,就可能會出現如下的情況:模組 MailboxAPI 和 XMLMailbox 被要求升級,而模組 FixedMailbox 為了穩定,要求不產生任何變化。然而當 XMLMailbox 的需求很大時,可能使 MailboxAPI 也產生了很大的變化,從而影響了 FixedMailbox,使之不能正常工作,於是版本衝突問題產生了。

    為了演示這個問題,我改變了模組 MailboxAPI 和 XMLMailbox 的程式碼,產生了版本 2.0 的 MailboxAPI 和 XMLMailbox 模組,他們被放置在附件 2(Version2.zip)中。為了看到真實的版本衝突問題,請解壓縮 Version2.zip,然後停止 WAS 並刪除 WAS 的 lib 目錄下的 MailboxAPI.jar,接著將 MailboxAPI2.jar 放置在 WAS 的 lib 目錄下,然後重啟你的 WAS 系統,接著你需要用 XMLMailboxReaderEAR2.ear 去升級已經存在的 XMLMailboxReader 應用,然後分別訪問兩個模組的 Servlet,你就會看到 FixedMailbox 模組將不能正常工作,而 XMLMailbox 則一切正常,如圖 7 或圖 8 所示。


    圖 7. FixedMailbox 不能正常執行
    圖 7. FixedMailbox 不能正常執行

    圖 8. XMLMailbox 可以執行
    圖 8. XMLMailbox 可以執行

    注意:如果你不刪除 MailboxAPI.jar,而是讓 MailboxAPI.jar 和 MailboxAPI2.jar 並存在 WAS 的 lib 目錄下,那麼就有可能是 FixedMailbox 能正常工作,而 XMLMailbox 不能正常工作,如圖 9 和圖 10 所示,原因是 MailboxAPI.jar 可能在 MailboxAPI2.jar 之前被載入到類載入器中了。總之,FixedMailbox 和 XMLMailbox 將不能同時工作,這就是版本衝突問題。


    圖 9. 當 MailboxAPI.jar 和 MailboxAPI2.jar 並存時,FixedMailbox 可以正常執行
    圖 9. 當 MailboxAPI.jar 和 MailboxAPI2.jar 並存時,FixedMailbox 可以正常執行

    圖 10. 當 MailboxAPI.jar 和 MailboxAPI2.jar 並存時,XMLMailbox 不能執行
    圖 10. 當 MailboxAPI.jar 和 MailboxAPI2.jar 並存時,XMLMailbox 不能執行

    講述如何將這個樣例程式中的某些元件轉變成 WAS 上的 OSGi bundle,從而解決了版本衝突問題

    為了解決在第五章中的版本衝突問題,我們只需要將模組 FixedMailbox 和 XMLMailbox 的業務邏輯部分(FixedMailbox.jar 和 XMLMailbox.jar)轉變成 OSGi 的 bundle,然後將 MailboxAPI 的版本 1 和版本 2(MailboxAPI.jar 和 MailboxAPI2.jar)也都轉變成 bundle,並使 FixedMailbox.jar 依賴於 MailboxAPI.jar,使 XMLMailbox.jar 依賴於 MailboxAPI2.jar。接下來,將 WAS 的 lib 目錄下的 MailboxAPI 模組移除掉,然後將 FixedMailbox.jar、XMLMailbox.jar、MailboxAPI.jar 和 MailboxAPI2.jar 同時放入 WAS 的 plugins 目錄下,最後重新啟動 WAS,你會發現模組 FixedMailbox 和 XMLMailbox 能同時工作,如圖 11 和圖 12 所示。


    圖 11. 在將 FixedMailbox.jar 轉變為 bundle 後,FixedMailbox 可以正常執行
    圖 11. 在將 FixedMailbox.jar 轉變為 bundle 後,FixedMailbox 可以正常執行

    圖 12. 在將 XMLMailbox.jar 轉變為 bundle 後,XMLMailbox 可以正常執行
    圖 12. 在將 XMLMailbox.jar 轉變為 bundle 後,XMLMailbox 可以正常執行

    在將整個郵件系統轉變成支援 OSGi bundle 後,會使郵件系統的各個模組之間的依賴關係從圖 4 程式設計圖 13 的情況,因為在 OSGi 中,同一模組的多個版本可以並存,並且模組之間的依賴關係可以通過版本進行限制,從而是 Java 應用軟體中的版本衝突問題得到了完美的解決。


    圖 13. 最後的郵件系統的模組之間的依賴關係
    圖 13. 最後的郵件系統的模組之間的依賴關係

    將已有的 Jar 檔案轉換成 bundle 非常簡單,我們不需要改變任何 Java 程式碼,只需要將必要的 OSGi 的 bundle 資訊加入 Jar 檔案的 MANIFEST.MF。為了方便大家的檢視,我將已經轉好的 bundle 檔案 FixedMailbox.jar、XMLMailbox.jar、MailboxAPI.jar 和 MailboxAPI2.jar 做成了附件 3。

    總結

    OSGi 框架是實現 Java 應用軟體模組化的重要手段。當前,OSGi 已經變的非常流行,很多的著名的 Java 應用在底層已經開始採用 OSGi 框架,比如說 Spring,IBM 的 Eclipse 和 WebSphere Application Server。所以將你自己的 Java 應用轉變成支援 OSGi 是必要的趨勢,而本文將給你一個範例,告訴你如何在 WebSphere Application Server 中將你的應用轉變成支援 OSGi,從而就可以避免版本衝突問題的發生。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14789789/viewspace-623410/,如需轉載,請註明出處,否則將追究法律責任。

相關文章