在 OSGi
服務平臺上構建應用時,必須考慮各種服務之間依賴關系及服務的管理,應用所依賴的服務有可能在任何時候被注銷或者更新,用戶在完成對 Service
的發布、查找、綁定的同時,還需要對服務的狀態進行監聽,以便作出適當的響應,所以在 OSGi 服務平臺上,對服務依賴關系的動態管理至關重要。在
OSGi Release 4 中,提出了 Declarative Services
規范,通過該規范可以方便地對服務之間的依賴關系和狀態進行監聽和管理。在本文中,將對 Declarative Services
規范進行介紹并且基于該規范開發一個實例。
Declarative Services簡介
Declarative Services 是一個面向服務的組件模型,它制訂的目的是更方便地在 OSGi
服務平臺上發布、查找、綁定服務,對服務進行動態管理,如監控服務狀態以及解決服務之間的復雜的依賴關系等問題。Declarative
Services 采用服務組件的延遲加載以及組件生命周期管理的方式來控制對于內存的占用以及啟動的快速,很好的解決了傳統的 OSGi
服務模型在開發和部署比較復雜應用時內存占用大、啟動慢等問題,并且對服務組件的描述采用XML來實現,十分便于用戶理解和使用。在
Declarative Services 中,Component 可以是 Service 的提供者和引用者,一個 Component 可以提供
0 至多個 Service,也可以引用 0 至多個 Service,并且采用component 方式封裝 Service,方便了對
Service 的復用,從開發者的角度來看,該服務組件模型簡化了在 OSGi 服務平臺中的編程模型。Declarative Services
規范參考了"Automating Service Dependency Management in a Service-Oriented
Component Model"一文的有關概念,讀者可從參考資料獲得該文的詳細信息。
Component Satisfied 概念介紹
在 Declarative Services 中,一個服務組件是包含在 Bundle 應用中的普通的 Java
類,每個Component 可以暴露出多個服務,同時也可依賴于多個服務,通過XML文件描述和服務組件相關的信息,SCR(Service
Component
Runtime)根據服務組件配置文件控制著組件配置的激活(Activate)和鈍化(Deactivate),服務組件配置文件包括如組件的類型、組
件的實現以及引用的服務等信息。在詳細介紹服務組件(Component)之前,我們必須了解 Component Satisfied 的概念,在
Declarative Services 中,Component Satisfied 與 Component 的生命周期密切相關。如
Component 激活的前提條件之一就是 Component Satisfied,而在 Component 的運行過程中,出現
Unsatisfied 時,Component 將被鈍化。主要由以下兩點決定 Component 是否處于Satisfied 狀態:
Component 為 Enabled 狀態,Component 的生命周期包含在引用它的 Bundle 應用的生命周期之內,只有在
Bundle 處于 Active 狀態時,Component 才有可能為 Enabled 狀態,在 Bundle處于 Stop
狀態時,Bundle 中所有的 Component 都處在 Disabled 狀態。Component 初始的Enabled
狀態可以在服務組件配置文件中設定。
Component 的配置是可以被引用和解析的,Component 中引用的 Service 也是 Satisfied 的,引用的
Service 至少有一個是處于可用狀態的,或者引用的 Service 在服務組件配置文件里配置了可為 0 個可用狀態的 Service。
當上述兩個條件中任何一個不滿足時,組件配置將變為 Unsatisfied 狀態,組件配置將被鈍化。在理解 Component Satisfied 的概念后,下面講解三種類型的服務組件。
Component 介紹
在 Bundle 啟動時, Declarative Services 裝載相應的服務組件配置文件,配置文件在MAINFEST.MF
文件的 Service-Component 屬性指定,解析配置文件,獲取服務組件引用的 Service ,如果判斷組件 Satisfied
狀態的兩個條件滿足時, Declarative Services 就認為這個組件是 Satisfied 的。
Immediate Component
對于 Immediate Component,如果組件配置處于 Satisfied 狀態,將會立即被激活,并且如果該配置指定了服務,那么
SCR 會注冊該服務并且立即激活該服務組件。在 SCR 激活組件配置時,實現服務組件類的 activate
方法將會被調用,在SCR鈍化組件配置時,deactivate方法將會被調用。Immediate Component的狀態圖如圖1所示:
圖示1:Immediate Component狀態圖
Delayed Component
對于 Delayed Component ,如果組件配置處于Satisfied狀態,該組件并不會立即被激活,Declarative
Services 會根據組件配置文件中的 Service 的配置,注冊相應的Service 的信息,直到該服務組件被請求時,
Declarative Services 才會激活該組件配置 。 Delayed Component 延遲了 Component
類的創建,當該服務組件的服務收到請求時,該 Component 類的 activate 方法才會被調用。如果一個 Component 不是
Factory Component,并且在其組件配置文件中指定了服務,組件的 immediate 屬性設置為 false,那么該組件就是
Delayed Component。Delayed Component 的狀態圖如圖 2 所示:
圖示2:Delayed Component 狀態圖
Factory Component
通過在組件配置文件中設置 Component 的 factory 屬性,將 Component 聲明為 Factory
Component。該組件在激活后注冊的是一個 Component Factory 服務,只有在調用 Component Factory 的
newInstance 方法后才會激活相應的各個組件,每一次調用 newInstance
方法,都會創建和激活一個新的組件配置。如果在組件配置文件中聲明了服務,那么在該組件激活之前,聲明的服務被注冊。Factory
Component 的狀態圖如圖3所示:
圖示3:Factory Component狀態圖
在三種類型的服務組件中,Delayed Component 很好的解決了系統服務的動態性問題,同時也節省了內存的占用。 服務組件的生命周期受 Bundle 生命周期影響,當 Bundle 停止時,那么Bundle 中所有的服務組件也就停止。
Service 的發布、查找、綁定
在 OSGi 服務平臺中,大部分 Bundle 應用都是基于服務的,服務的發布、引用十分重要,下面講一下利用服務組件如何進行 Service 的發布、查找和綁定。
Service 的發布
對于 Component 中 Service 的發布,需要在組件配置文件中定義 service 元素,該
service元素至少包括一個或多個 provide 元素,該 provide 元素定義了該 component
提供的服務接口,它只有一個屬性 interface,該 interface
定義了提供服務的接口,并且允許是實現該服務接口的類名。可以看出,利用 Declarative Services 發布 Service
非常簡單,只要 Component 實現了定義的 Service 的接口即可。如在本文所講解例子中,在組件配置文件中,聲明姓名查詢服務如圖 4
所示:
圖示4:姓名查詢服務聲明
Service 的查找和綁定
在 Declarative Services 中,Component 所引用的服務,稱為 Target Service,當
Component 中引用的 Target Service 也是 Satisfied 時,即引用的 Service
至少有一個是處于可用狀態的,或者引用的 Service 在服務組件配置文件里配置了可為 0 個可用狀態的
Service,組件配置才有可能被激活。在組件實現類中,有兩種策略可以獲得在組件配置文件里指定的 Target Service,是事件策略和
Lookup 策略。
事件策略
在服務組件激活的過程中,SCR 必須將組件配置文件里指定的 Target Service 綁定到組件配置中。在事件策略中,SCR
通過調用組件實現類的一個方法將 Target Service 綁定到組件中,同樣,SCR
通過調用另外一個方法來取消綁定,這些方法在組件配置文件中 reference 元素的bind 和 unbind
屬性指定。事件策略主要適用于服務組件所引用的 Target Service
處在動態變化中。如在本文例子中,如果采用事件策略引用姓名查詢服務,在配置文件中聲明和 Component 實現類中引用服務分別如圖示 5、圖示
6 所示:
圖示5:采用事件策略的組件配置文件
圖示 6:采用事件策略的綁定姓名查詢服務
Lookup 策略
在組件實現類中,通過調用 ComponentContext 的 locateService 方法來定位所引用的 Target
Service ,該方法的參數是在組件配置文件里指定的 reference 元素的 name 屬性。如在本文例子中,如果采用 Lookup
策略引用姓名查詢服務,在配置文件中聲明和 Component實現類中引用服務分別如圖示 7、圖示 8 所示:
圖示7:采用 Lookup 策略的組件配置文件
圖示 8:采用 Lookup 策略的引用姓名查詢服務
在 OSGi 服務平臺中,即便 Component 已經綁定所引用的 Target
Service,但是由于服務的動態性,它可能在任何時刻被注冊、替換或者注銷,這些變化可能使服務組件所引用的 Target Service
變成過時的引用,所以,在 Declarative Services
中,當這些情況發生時,Component必須采取某種策略去處理這些變化。Declarative Services 提供兩種策略,一種是
static 策略 ,另外一種是 dynamic 策略 ,默認情況下 Component 采用的是 static 策略。當采用static
策略時,如果引用的 Target Service 發生了變化,那么組件配置會被重新裝載并激活。當采用 dynamic 策略時,SCR
在不鈍化組件配置的情況下可以改變綁定的 Target Service。此外,Declarative Services
還提供很多功能,如可通過在 Component 的 reference 元素中增加 target 屬性來實現對所引用 Service
進行過濾;如可增加 cardinality 屬性來對引用 Service 的數量進行控制。關于 Declarative Services
更詳細的信息,請讀者參見本文的參考資料。
使用 Eclipse 開發服務組件
在本文中,我們結合 Equinox 項目關于 Declarative Services 的實現,開發兩個使用服務組件的 Bundle
應用,其中第一個 Bundle 的服務組件的配置文件中聲明注冊了一個姓名查詢服務,用于判斷所給姓名是否在已定義的查詢列表中;第二個
Bundle 應用的服務組件的配置文件中靜態引用了第一個 Bundle
應用服務組件所注冊的姓名查詢服務,如果用戶所給的姓名包含在查詢列表中,將返回正確的信息。最后,將開發的 Bundle 應用部署的
Equinox OSGi 框架中,用戶可以在 OSGi 控制命令行中輸入命令來查詢關于框架和 Bundle
應用的具體信息。讀者可以從參考資料中獲得本文 Bundle 應用的源代碼。關于 Equinox 項目的詳細信息,請查閱參考資料信息。
(1)首先定義所提供服務的接口,然后 Bundle 應用的服務組件實現這個服務接口。在本例中,定義姓名查詢接口 NameService.java。下面是該接口的源代碼:
NameService Interface 源代碼
package ds.example.service;
/**
* A simple service interface that defines a name service.
* A name service simply verifies the existence of a Name.
**/
public interface NameService {
/**
* Check for the existence of a Name.
* @param name the Name to be checked.
* @return true if the Name is in the list,
* false otherwise.
**/
public boolean checkName(String name);
}
|
該服務接口很簡單,只包含一個需要實現的方法。通常為了將服務接口和服務實現相分離,要將該服務接口單獨放在一個包內。
(2 ) 定義 Bundle 描述文件 MANIFEST.MF,Bundle 應用 dsExample 的 MANIFEST.MF 文件如下:
MANIFEST.MF 文件信息
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: DsExample Service
Bundle-SymbolicName: dsExample
Bundle-Version: 1.0.0
Bundle-Localization: plugin
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.service.component;version="1.0.0"
Service-Component: OSGI-INF/component.xml
Export-Package: ds.example.service
|
其中,Service-Component 屬性指定了該 Bundle
應用的服務組件配置文件,在該配置文件中聲明服務并且指定了實現該服務的組件;Export-Package 屬性指定了該 Bundle
輸出的共享包,該屬性可以使其他的 Bundle 應用引用所定義的服務接口。
(3)編輯該 Bundle 應用的服務組件配置文件,正如前面所講 Service 的發布那樣,該服務組件的配置文件如下:
dsExample Bundle 的組件配置文件
<?xml version="1.0" encoding="UTF-8"?>
<component name="dsExample">
<implementation
class="example.osgi.NameImpl"/>
<service>
<provide interface="ds.example.service.NameService"/>
</service>
</component>
|
其中,Service 元素定義了所提供服務的接口;Implementation 元素定義了實現該服務接口的組件類名。
(4)實現在服務組件配置文件中指定的服務組件,源代碼如下所示:
dsExample 實現組件源代碼
public class
NameImpl
implements NameService {
// The set of names contained in the arrays.
String[] m_name =
{ "Marry", "John", "David", "Rachel", "Ross" };
protected void activate(ComponentContext context) {
System.out.println("NameService Component Active,within the bundle
lifecircle.");
}
public void
deactivate(ComponentContext context)
throws Exception {
System.out.println("NameService Component Deactive,within the bundle
lifecircle.");
}
public boolean checkName(String name) {
// This is very inefficient
for
(
int i = 0; i < m_name.length; i++)
{
if (m_name[i].equals(name))
{
return true;
}
}
return false;
}
}
|
該服務組件實現了 NameService 接口,并且在服務組件激活和鈍化時分別打印出相應信息,以便在運行 Bundle 應用時,能夠跟蹤 Component 的生命周期。
(5)創建項目名為 dsExampleClient 的 Bundle 應用,該應用的服務組件在 OSGi 平臺上查詢并引用
dsExample Bundle 應用已經注冊的姓名查詢服務,然后從標準輸入讀入用戶所輸入的姓名信息,判斷所輸入姓名是否有效。關于
dsExampleClient 應用,讀者可從參考資料中獲得完整源代碼,下面只給出該 Bundle 應用的服務組件配置文件:
dsExampleClient Bundle 的組件配置文件
<?xml version="1.0" encoding="UTF-8"?>
<component name="dsExampleClient">
<implementation
class="exampleclient.osgi.CheckNameClient"/>
<reference name="nameservice"
interface="ds.example.service.NameService"
cardinality="1..1"
policy="static"
/>
</component>
|
其中,reference 元素定義了該組件所引用的服務接口,并且指明該組件采用 static 策略;Implementation 元素指定了實現組件的類。
Bundle的部署及運行
在 Eclipse 平臺中,在菜單中選擇 Run-->Run AS-->Equinox FrameWork 來啟動 OSGi
服務平臺。注意在Equinox啟動配置控制臺中的Target Platform
中選擇org.eclipse.equinox.ds選項,該plug-in是Equinox關于Declarative
Services的實現,將兩個Bundle應用設置為取消自動啟動選項。當OSGi Equinox
FrameWork啟動后,在OSGi控制命令臺中輸入ss命令,可以查看OSGi服務平臺中已經安裝的Bundle應用信息及其狀態。如圖9所示,可以
看到dsExample和dsExampleClient Bundle應用處于Resolved狀態。
圖示9:Bundle狀態查詢
在OSGi控制命令臺中利用start命令啟動 dsExample 應用,用ss命令查看啟動后的Bundle應用信息及其狀態,可以看出
dsExample Bundle
處于Active狀態,但是該Bundle的服務組件并沒有被激活,如果被激活,將會在OSGi控制命令臺中打印出"NameService
Component Active,within the bundle lifecircle."字樣,說明該服務組件為 Delayed
Component 類型,該組件并不會立即被激活,直到該服務組件被請求時, Declarative Services
才會激活該組件配置,Delayed Component延遲了組件的加載,節省了內存的占用 ,如圖10所示:
圖示10:啟動dsExample Bundle
在OSGi控制命令臺中利用start命令啟動dsExampleClient應用,可以看出兩個 Bundle的服務組件相繼被激活,如圖11所示:
圖示11:啟動dsExampleClient Bundle
在OSGi控制命令臺中利用stop命令停止dsExample應用,可以看出兩個 Bundle的服務組件相繼被鈍化,如圖12所示:
圖示12:停止dsExample Bundle
需要注意的是服務組件的生命周期受 Bundle 生命周期的影響,當 Bundle 停止時,那么Bundle 中所有的服務組件也就停止。
總結
Declarative Services 是一個面向服務的組件模型,其目的是更方便地在 OSGi
服務平臺上發布、查找、綁定服務,對服務進行動態管理。Declarative Services
采用服務組件的延遲加載以及組件生命周期管理的方式來控制對于內存的占用以及啟動的快速,對 Service
的動態管理,使得系統可以根據系統運行的情況做出及時的響應,增強了系統的穩定性和靈活性。項目Gravity
也采用了類似的機制,有興趣的讀者可以參見參考資料中的詳細信息。