前一篇文章,《模組化Java: 動態模組化》描述了如何通過使用服務 (service)給應用程式帶來動態模組化特性。它們是通過輸出的一個(或多個 )可以在運行時被動態發現的介面而實現的。儘管這種方式使得client和server 完全解耦,但是又帶來一個如何(何時)啟動服務的問題。
啟動順序
在徹頭徹尾的動態系統裡,服務不僅可以在系統啟動並執行時候裝卸,還可以以 不同的順序啟動。有時,這是個大問題:無論A和B的啟動順序如何,在系統達到 就緒狀態並準備好接收事件之前,如果沒有事件(或線程)出現,那麼哪個服務 先啟動都無大礙。
可是,有很多情況都不符合這一簡單假設。經典的例子就是logging: 通常, 服務在啟動和做其他動作的時候,就要串連並開始寫日誌了。如果Log Service此時 還不可用,那會有什麼後果?
假定服務在運行時能夠動態裝卸,client應該能夠應對服務不存在時的情況 。在這種情況下,它也許能聰明地轉移到另一種機制(如輸出到標準輸出),或 者處於阻塞狀態等待服務可用(對logging系統來說不是好的答案)。可是,讓 服務啟動之前就可用是不切實際的。
啟動層級
OSGi提供了一種機制來控制bundle啟動時的順序,即使用啟動層級(start levels)。這一概念是基於UNIX運行層級的概念:系統以層級1啟動,然後單調 遞增,直到達到目標啟動層級。每個OSGi容器都提供了不同的預設目標層級: Equinox預設值是6;而Felix是1。
啟動層級可被用來建立bundle間的啟動順序,讓關鍵bundle服務(比如 logging)的啟動層級比那些需要用它的bundle更低。可是因為可 用的啟動層級 值是有限的,而且安裝程式傾向於選擇單一數字作為啟動層級,因此它並不能確 保你僅通過啟動順序就能解決問題。
另一點值得注意的是,具有相同啟動層級的bundle是各自獨立啟動的(可能 並行),因此,如果你有一個與log服務具有相同啟動層級的bundle,誰也不能 保證log服務能夠在需要的時候已經就緒。換句話說,啟動層級可以解決大部分 問題,但不能解決所有問題。
聲明式服務
解決這一問題的一個方案是OSGi的聲明式服務(以下稱為DS——declarative services)。用這一方法,各個組件是由外部bundle將他們組織在一起並決定他 們什麼時候可用。聲明式服務是通過在一個XML設定檔群組織在一起的,檔案中 描述了需要(消費)或提供什麼服務。
在上篇文章最後一個例子中,我們使用ServiceTracker去獲得服務,如果必 要則需等待服務可用。如果我們把建立shorten命令延遲到shortening服務可用 之後會很有用。
DS定義了一個組件(component)概念,其是比bundle更細粒度的概念,但是 比服務的概念粒度更大一些(因為一個組件可以消費/提供多個服務)。每個組 件都有一個名字,對應一個Java類,並可以通過調用該類的方法使其啟用或失效 。與OSGi Java API不同,DS允許用純Java POJO來開發組件,根本不需要從程式 上依賴OSGi。其附帶的好處是讓DS更加易於測試和類比(test/mock)。
為了說明這一方法,我們將繼續使用前面的例子。我們需要兩個組件:一個 是shortening服務本身,另一個是調用它的ShortenComand。
第一項任務是用DS配置並註冊shorten服務。我們可以讓DS在服務啟動時註冊 它,而不是通過Bundle-Activator註冊該服務。
那麼DS怎麼知道要啟用並串連誰呢?我們需要給Bundle的Manifest頭增加一 個條目,其指示了一個(或多個)XML組件定義檔案。
Bundle-ManifestVersion: 2
...
Service-Component: OSGI-INF/shorten-tinyurl.xml [, ...]*
這個 OSGI-INF/shorten-tinyurl.xml組件定義檔案內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component name="shorten-tinyurl" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.infoq.shorten.tinyurl.TinyURL"/>
<service>
<provide interface="com.infoq.shorten.IShorten"/>
</service>
</scr:component>
當DS處理這一組件時,其效果與代碼context.registerService( com.infoq.shorten.IShorten.class.getName(), new com.infoq.shorten.tinyurl.TinyURL(), null );基本一樣。Trim()服務需要類 似的聲明,在下面的原始碼中包含著這部分內容。
如果需要的話,一個單一組件可以基於不同介面提供多個服務。一個bundle 也可以包含多個組件,使用相同或不同的類,每個都提供不同的服務。