OSGi 中的 Declarative Services 規範簡介

在 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狀態圖
圖示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 狀態圖
圖示2:Delayed Component 狀態圖

Factory Component

通過在組件配置文件中設置 Component 的 factory 屬性,將 Component 聲明爲 Factory Component。該組件在激活後註冊的是一個 Component Factory 服務,只有在調用 Component Factory 的 newInstance 方法後纔會激活相應的各個組件,每一次調用 newInstance 方法,都會創建和激活一個新的組件配置。如果在組件配置文件中聲明瞭服務,那麼在該組件激活之前,聲明的服務被註冊。Factory Component 的狀態圖如圖3所示:


圖示3:Factory Component狀態圖
圖示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:姓名查詢服務聲明
圖示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:採用事件策略的組件配置文件
圖示5:採用事件策略的組件配置文件

圖示 6:採用事件策略的綁定姓名查詢服務
圖示 6:採用事件策略的綁定姓名查詢服務

Lookup 策略

在組件實現類中,通過調用 ComponentContext 的 locateService 方法來定位所引用的 Target Service ,該方法的參數是在組件配置文件裏指定的 reference 元素的 name 屬性。如在本文例子中,如果採用 Lookup 策略引用姓名查詢服務,在配置文件中聲明和 Component實現類中引用服務分別如圖示 7、圖示 8 所示:


圖示7:採用 Lookup 策略的組件配置文件
圖示7:採用 Lookup 策略的組件配置文件

圖示 8:採用 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狀態查詢
圖示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
圖示10:啓動dsExample Bundle

在OSGi控制命令臺中利用start命令啓動dsExampleClient應用,可以看出兩個 Bundle的服務組件相繼被激活,如圖11所示:


圖示11:啓動dsExampleClient Bundle
圖示11:啓動dsExampleClient Bundle

在OSGi控制命令臺中利用stop命令停止dsExample應用,可以看出兩個 Bundle的服務組件相繼被鈍化,如圖12所示:


圖示12:停止dsExample Bundle
圖示12:停止dsExample Bundle

需要注意的是服務組件的生命週期受 Bundle 生命週期的影響,當 Bundle 停止時,那麼Bundle 中所有的服務組件也就停止。

 


總結

Declarative Services 是一個面向服務的組件模型,其目的是更方便地在 OSGi 服務平臺上發佈、查找、綁定服務,對服務進行動態管理。Declarative Services 採用服務組件的延遲加載以及組件生命週期管理的方式來控制對於內存的佔用以及啓動的快速,對 Service 的動態管理,使得系統可以根據系統運行的情況做出及時的響應,增強了系統的穩定性和靈活性。項目Gravity 也採用了類似的機制,有興趣的讀者可以參見參考資料中的詳細信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章