OSGi開發起步(Getting Started with OSGi)-1第一個OSGi模塊
原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi
http://hi.baidu.com/tinawhisper/home
本文系翻譯文章,但并未嚴格按照原文翻譯。
1. 第一個OSGi模塊
本文期望展示OSGi開發環境的簡潔性,因此這里沒有使用eclipse開發環境。本文只使用文本編輯器和基本的命令行工具進行OSGi程序開發。
本文的第一個例子相對其他示例有些長,這是因為我們需要建立一個基本的工作環境。在開始之前,我們需要一個可以運行的OSGi框架。現在我們可以在三種開源框架中進行選擇:Apache Felix,Knopflerfish和Equinox。本文所有編寫的代碼對以上三個框架來說都是可用的,只是在使得這些代碼運行的指令上有所區別。本文選擇eclipse使用的Equinox。首先需要找到eclipse安裝目錄下的org.eclipse.osgi_3.2.1.R32x_v20060919.jar并復制此文件到一個空目錄(文件中的版本號會根據安裝eclipse的不同而不同)。為了使命令行參數短一些,把剛才復制過來的jar文件更名為equinox.jar。現在就可以在命令行中定位到剛才那個空目錄并執行這個命令:
> java -jar equinox.jar -console
幾秒鐘后,命令窗口中會出現osgi>提示。如果是這樣,就說明OSGi已經運行起來了(意外總會發生,比如你沒有安裝java!)。
osgi>命令行提示允許我們運行Equinox中的命令來進行整個框架的控制。可以在提示符后輸入help獲得所有命令的列表。經常使用的一個命令是ss。ss表示簡要狀態(short status)的意思。ss會列出當前已經安裝到框架中所有模塊(bundle)和它們狀態的列表。在OSGi中bundle表示一個模塊,這與eclipse中的插件(plug-ins)具有等同的概念。
輸入ss后,Equinox輸出為:
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
以上輸出告訴我們當前只安裝了一個已經啟動的模塊:系統模塊(System bundle)。此模塊是OSGi的一個總是安裝并處于活動狀態的特殊模塊,它代表了框架本身。
現在我們開始編寫一個模塊。在相同的目錄下,創建一個名稱為HelloActivator.java的文件并復制以下代碼到文件中:
import org.osgi.framework.*;
public class HelloActivator implements BundleActivator {
public void start(BundleContext context) { System.out.println("Hello Eclipse!"); }
public void stop(BundleContext context) { System.out.println("Goodbye Eclipse!"); }
}
一個模塊還需要一個元文件(manifest file)對它的各種元信息進行聲明:如模塊的名稱,版本等信息。所以現在還需要創建一個名稱為HelloWorld.mf的文件并復制下文的文本到此文件中。特別需要注意的是要在此文本文件的最后保留一個空白行,否則打包命令jar在打包時會截短文件(最后一行丟失)。這樣就會使得生產的jar包中的元文件缺失一行,導致jar文件讀取和運行失敗。
Manifest-Version: 1.0
Bundle-Name: HelloWorld
Bundle-Activator: HelloActivator
Bundle-SymbolicName: HelloWorld
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework
現在另外打開一個命令行窗口使用如下命令構建一個jar文件:
> javac -classpath equinox.jar HelloActivator.java
> jar -cfm HelloWorld.jar HelloWorld.mf HelloActivator.class
回到原先的OSGi控制臺,輸入install file:HelloWorld.jar。控制臺會輸出"Bundle id is 1" ,接著輸入ss命令就可以得到如下輸出信息:
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
1 INSTALLED HelloWorld_1.0.0
剛才開發的HelloWorld模塊安裝到了OSGi框架中,但此模塊還沒有運行起來。現在輸入命令start 1。命令中的1是安裝HelloWorld模塊完成后,OSGi賦值給此模塊的ID號。當運行啟動命令后,控制臺顯示消息"Hello Eclipse!"。接著輸入stop 1就會得到消息"Goodbye Eclipse!"。
這些命令都做了些什么事情?上面的代碼實現了一個BundleActivator接口,這個接口用于允許框架通知我們一些重要的生命期事件。當模塊啟動時,框架調用start方法,而如果模塊停止時,框架則調用stop方法。還有一個要說明的是元文件中有一行聲明"Bundle-Activator: HelloActivator"就是用于通知框架在一個模塊中那個類是啟動類(activator)。通常我們使用一個完整的類名稱,但在此示例中為了簡單而只使用的缺省包名。
術語表:
Bundle: 模塊
Activator:啟動器
Component:組件
Service:服務
Sevice Register:服務注冊表
Declarative Service: 服務聲明,簡稱DS
OSGi Framework: OSGi框架或者框架
manifest file: 元文件
=========================================================================================
OSGi開發起步(Getting Started with OSGi)-2與OSGi框架進行交互
原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi
本文系翻譯文章,但并未嚴格按照原文翻譯。
2. 與OSGi框架進行交互
上一節舉了一個簡單的示例模塊HelloWorld:啟動和停止時輸出消息的模塊。該模塊通過實現BundleActivator接口定義的start和stop函數方法完成了這些消息的輸出。如果回過頭來仔細的研究start和stop方法的定義,我們可以發現函數定義了一個BundleContext類型的參數。在本節描述參數BundleContext以及它的用法。
BundleContext是OSGi框架提供給一個模塊的'通行票據'。當模塊需要與框架進行交互和通信時,就可以使用BundleContext。實際上,這也是一個模塊可以唯一與OSGi框架進行交互的渠道。OSGi框架在每個模塊啟動時會通過此模塊的BundleActivator派送一個票據:BundleContext。
如果上一節的OSGi控制臺還在運行狀態,下面的工作就不需要重啟控制臺。如果控制臺關閉了就需要使用下面的命令啟動它。
> java -jar equinox.jar -console
輸入ss后就會發現上次安裝過的HelloWorld模塊。即便是OSGi框架關閉重啟了之后,HelloWorld模塊也會保持上節的狀態。這主要是因為OSGi框架進行了模塊的持久化存儲處理。
在這一節我們完成一個查找并卸載HelloWorld模塊的模塊。在OSGi控制臺可以使用uninstall命令簡單的完成這項任務。但是這一節希望通過OSGiAPI編碼的方式實現此任務。
創建一個名稱為HelloWorldKiller.java的文件,輸入下面的代碼:
import org.osgi.framework.*;
public class HelloWorldKiller implements BundleActivator {
public void start(BundleContext context) {
System.out.println("HelloWorldKiller searching...");
Bundle[] bundles = context.getBundles();
for(int i=0; i<bundles.length; i++) {
if("HelloWorld".equals(bundles[i]
.getSymbolicName())) {
try { System.out.println("Hello World found, " + "destroying!"); bundles[i].uninstall(); return; } catch (BundleException e) { System.err.println("Failed: " + e.getMessage()); }
}
}
System.out.println("Hello World bundle not found");
}
public void stop(BundleContext context) { System.out.println("HelloWorldKiller shutting down"); }
}
接著創建此模塊的元文件,注意文件最后要留一個空白行。在元文件中輸入以下內容:
Manifest-Version: 1.0
Bundle-Name: HelloWorldKiller
Bundle-Activator: HelloWorldKiller
Bundle-SymbolicName: HelloWorldKiller
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework
編譯和構建模塊對應的jar文件:
> javac -classpath equinox.jar HelloWorldKiller.java
>jar -cfm HelloWorldKiller.jar HelloWorldKiller.mf HelloWorldKiller.class
在控制臺安裝構建完成的新模塊:install file:HelloWorldKiller.jar。然后輸入ss。模塊的狀態類似以下列表:
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
1 ACTIVE HelloWorld_1.0.0
2 INSTALLED HelloWorldKiller_1.0.0
接著啟動我們的新模塊HelloWorldKiller:start 2。其輸出信息為:
HelloWorldKiller searching...
Hello World found, destroying!
Goodbye Eclipse!
注意最后一行是先前那個HelloWorld模塊的輸出信息。當這個模塊處于運行狀態并且啟動HelloWorldKiller時,HelloWorld模塊被停止并進行了卸載。這種操作觸發了HelloWorld模塊啟動器的stop方法。
輸入命令ss就會發現HelloWorld模塊消失了。
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
2 ACTIVE HelloWorldKiller_1.0.0
這里的問題是:安全性如何保證?好像任何模塊都可以卸載其他模塊。實際上OSGi定義了一個復雜的安全層次機制。這個安全機制提供了對模塊與框架交互的精確控制。這樣就可以把卸載模塊的權限限制在某些特定的管理模塊。重要的是OSGi進行安全控制基本上是一個配置過程,所以在這個講解系列里我們關注點主要放在編碼上。
在開始下節講解之前,我們可以了解一下BundleContext接口,看看此接口提供了哪些方法可供使用。比如,我們可以使用installBundle方法安裝一個模塊到OSGi中。或者也可以獲取當前所有安裝的模塊列表并打印這些模塊最近一次修改的日期和時間。具體的方法可以參考OSGi R4的API javadoc .
術語表:
Bundle: 模塊
Activator:啟動器
Component:組件
Service:服務
Sevice Register:服務注冊表
Declarative Service: 服務聲明,簡稱DS
OSGi Framework: OSGi框架或者框架
manifest file: 元文件
=========================================================================
OSGi開發起步(Getting Started with OSGi)-3 模塊間依賴
3. 模塊間依賴
上兩節展示了模塊的啟動和停止以及模塊與系統框架的交互方式。但是問題是模塊真正的用途是什么呢?
模塊是功能集合。模塊使我們能夠把龐大紛雜的項目劃分成可管理的單元。這些單元可以方便的載入到OSGi運行環境。問題是,不管我們喜歡還是不喜歡,模塊幾乎總是依賴于其他模塊。對jar文件來說,從來沒有一個可靠的方式來指定不同jar之間的依賴關系(注意,jar元文件中的類路徑也不是一個可靠的方法)。因此你永遠不能真正的確定: Jar中的代碼會正常工作?還是會在運行時失控拋出一個ClassNotFoundException異常?
OSGi以簡潔的方式解決了這個問題。僅僅這樣說還不如通過功能展示來的更有說服力些。所以讓我們馬上開始編碼來做到這點。
不幸的是直至現在,我們一直使用默認包的方式進行類的開發。這種方式不適應更復雜問題的編碼,所以現在需要使用java包的形式進行編碼開發和功能劃分。首先以一個非常簡單的JavaBean類開始,復制以下代碼到文件osgitut /movies/ Movie.java :
package osgitut.movies;
public class Movie {
private final String title;
private final String director;
public Movie(String title, String director) { this.title = title; this.director = director; }
public String getTitle() { return title; }
public String getDirector() { return director; }
}
現在接著在此包內創建一個接口。復制以下代碼到文件osgitut /movies/ MovieFinder.java :
package osgitut.movies;
public interface MovieFinder { Movie[] findAll(); }
然后把這兩個類封裝為一個模塊。當然,這個模塊十分簡單而且用途也不大。但是對于本節探討的內容已經足夠了。同前面一樣,模塊還必要有一個對應的元文件MoviesInterface.mf:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movies Interface
Bundle-SymbolicName: MoviesInterface
Bundle-Version: 1.0.0
Export-Package: osgitut.movies;version="1.0.0"
文件中多了一行:Export-Package。這個屬性表明包osgitut.movies從這個模塊中導出。而問題是java的jar文件中所有的東西都是導出的。但是我們難道不想只導出包中的部分代碼而保持其他部分只在包內部可見嗎?我們當然可以使用private或者protected限制類的訪問范圍,而這樣一來同一個jar中其他包也不能訪問這些類了。所以OSGi有效地引進了一種新的代碼保護級別:如果模塊中的包名沒有在Export-Package頭中列出,那么此包就只能在這個模塊中才能訪問到。
此外,我們還在導出包名稱后掛接了軟件版本號。在下一個階段的講解中我們會發現這十分重要。當然,這也不是絕對有必要提供一個版本。但如果你不指定版本號時,OSGi將自動為導出的包指定版本" 0.0.0 "。始終為導出的包指定明確的版本是一個優秀的實踐經驗。
現在構建此模塊:
> javac osgitut/movies/Movie.java osgitut/movies/MovieFinder.java
> jar -cfm MoviesInterface.jar MoviesInterface.mf osgitut/movies/*.class
完成此項工作后我們不要急于安裝此模塊。在這之前我們還需要構建此模塊的一個依賴模塊。即一個實現了MovieFinder接口的具體實現類:osgitut/movies/impl/BasicMovieFinderImpl.java :
package osgitut.movies.impl;
import osgitut.movies.*;
public class BasicMovieFinderImpl implements MovieFinder {
private static final Movie[] MOVIES = new Movie[] { new Movie("The Godfather", "Francis Ford Coppola"), new Movie("Spirited Away", "Hayao Miyazaki") };
public Movie[] findAll() { return MOVIES; }
}
還需要一個元文件:BasicMovieFinder.mf。
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Basic Movie Finder
Bundle-SymbolicName: BasicMovieFinder
Bundle-Version: 1.0.0
Import-Package: osgitut.movies;version="[1.0.0,2.0.0)"
注意,在這里我們導入了一個由其他模塊導出的包osgitut.movies。而且還同時為此導入包設置了版本范圍。框架在運行時刻使用此版本范圍匹配一個適合的導出包。OSGi表達版本范圍使用的是通用的數學式語法:方括號表示包含,圓括號表示排除。在上面這個示例元文件中我們使用這個語法有效的指定了一個1.x的版本。
對本節示例來說,在導入部分加入版本限制并不是必需的。但這是一個通用的實踐準則。
現在按照以下命令構建第二個模塊:
> javac -classpath MoviesInterface.jar osgitut/movies/impl/BasicMovieFinderImpl.java
> jar -cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
最后我們可以準備在OSGi環境中試驗這兩個模塊。第一步就是安裝BasicMovieFinder模塊并運行ss命令。這時此模塊的狀態顯示為INSTALLED。
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
(說明,你的模塊列表可能與上面這個示例列表存在一定的差異。實際上,模塊id號會根據你上次安裝和卸載HelloWorld模塊的次數而確定。因此下面示例中的id號要根據你運行環境中相應的id進行替換。)
INSTALLED表示框架載入了這個模塊,但還沒有完成此模塊依賴關系的解析。一個讓框架執行模塊依賴解析的命令是refresh。這里輸入refresh 4然后是ss命令就可以看到:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
這個模塊沒有解析成功并仍然處于INSTALLED狀態。這個問題出在我們并沒有安裝其依賴的那個包含Movie類和MovieFinder接口的模塊。如果想確認是不是這個問題,在框架控制臺輸入diag 4以獲取此模塊的調試信息:
file:BasicMovieFinder.jar [4]
Missing imported package osgitut.movies_[1.0.0,2.0.0).
看來確實是因為框架運行環境沒有任何可供導出的osgitut.movies包。那么現在就安裝MoviesInterface.jar模塊并運行ss。大致的輸出結果為:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
5 INSTALLED MoviesInterface_1.0.0
最后一步就是通知框架重新執行BasicMovieFinder模塊的依賴解析。依舊使用refresh 4命令。輸出的結果為:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 RESOLVED BasicMovieFinder_1.0.0
5 RESOLVED MoviesInterface_1.0.0
BasicMovieFinder模塊現在處于已解析狀態。這是一個基本步驟。因為只有處于已解析的模塊才能啟動。
需要注意的是通常情況下并不需要使用本節展示的手工方式進行模塊依賴解析。一般來說模塊會在它們需要的時候自動進行解析。例如,我們可以注意到MoviesInterface模塊也處理已解析狀態,而我們并沒有針對此模塊顯示的執行refresh命令。
下一節我們進行OSGi服務開發。
=======================================================================================
OSGi開發起步(Getting Started with OSGi)-4注冊一個服務
4 注冊一個服務
我們終于可以開始OSGi服務的講解了。在我看來,服務層是OSGi中最具特點的部分。在接下來的幾節你就會充分體會到這一點。
上節講解的是MovieFinder接口的例子。這個接口被MovieLister用來查找電影。這個例子是一個Ioc(控制反轉)的例子。
MovieLister并不太關心原始電影數據的來源,所以我們使用了MovieFinder接口隱藏了這些細節。關鍵在于我們可以使用另外一種實現替代現有的MovieFinder:比如從數據庫或者亞馬遜web服務進行電影查詢。而MovieLister只依賴與接口而不是具體的實現。
雖然不錯,但是有些時候我們實際上還是要把一個MovieFinder的具體實現明確指定給MovieLister。我們是用一個外部容器把這個具體實現"推"給了MovieLister,而不是讓MovieLister自己調用一個查找方法發現這個具體實現對象。
這就是IoC術語的由來。現在存在大量的這類外部容器,如PicoContainer,HiveMind,Spring以及EJB3.0。但是直至現在這些容器都有一個基本限制:它們都是靜態的。一旦一個MovieFinder指定給了MovieLister,兩者在整個JVM虛擬機生命周期內都一直關聯。
OSGi支持具有動態特性的IoC模式。OSGi支持動態的提供給MovieLister一個MovieFinder的實現然后移除它們。這樣我們就能在一個支持文本電影數據查詢的應用程序和一個依賴亞馬遜web服務電影數據查詢的應用程序之間進行熱切換。
OSGi服務層用于支持以上特性。我們要做的只是在服務注冊表中注冊MovieFinder為其中的一個服務就能支持熱切換特性。這可是相當簡單啊。同樣的MovieLister的功能也可以由一個MovieLister服務提供。一個服務與一個普通的java對象(POJO)沒有什么不同,它只是使用java接口名進行了服務注冊而已。
在本節我們就看看如何在注冊表中注冊服務。接下來就試試怎樣編碼從注冊表獲取服務并提供給一個MovieLister。
在上節代碼基礎上增加一個BasicMovieFinder。不需要更改任何現有的類,只是在增加一個啟動器類。下面就是這個啟動類osgitut/movies/impl/BasicMovieFinderActivator.java:
package osgitut.movies.impl;
import org.osgi.framework.*;
import osgitut.movies.*;
import java.util.Properties;
import java.util.Dictionary;
public class BasicMovieFinderActivator implements BundleActivator {
private ServiceRegistration registration;
public void start(BundleContext context) { MovieFinder finder = new BasicMovieFinderImpl(); Dictionary props = new Properties(); props.put("category", "misc"); registration = context.registerService( MovieFinder.class.getName(), finder, props); }
public void stop(BundleContext context) { registration.unregister(); }
}
接下來就是修改BasicMovieFinder.mf文件:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Basic Movie Finder
Bundle-SymbolicName: BasicMovieFinder
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.BasicMovieFinderActivator
Import-Package: org.osgi.framework,
osgitut.movies;version="[1.0.0,2.0.0)"
在這個元文件中相對上節變化了兩行。第一個增加了Bundle-Activator行。此行描述當前模塊使用的啟動模塊(以前我們并沒有使用)。此外還在導入包行中增加了org.osgi.framework。本節以前版本的模塊并沒有與框架進行交互,因此也就不需要導入OSGiAPI相關的包。
現在重新構建BasicMovieFinder.jar。
> javac -classpath equinox.jar:MoviesInterface.jar osgitut/movies/impl/*.java
> jar cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
回到OSGi控制臺,上節安裝的BasicMovieFinder.jar還在框架中運行。因此要更新這個模塊,運行update N命令,N是這個模塊的ID(使用ss可以得到此ID)。接著使用start N命令啟動此模塊。接著我們看到什么-基本上也沒看到些什么。
實際上,模塊已經在OSGi服務注冊表中注冊了,但是并沒有其他人在另外一端要使用此服務。因此這個服務的注冊過程并沒有產生任何可視效果。如果我們想向自己保證我們的代碼確實運行了,也大可不必掘地三尺。執行下面這個命令就可以了:
services (objectClass=*MovieFinder)
我們會看到下面的輸出:
{osgitut.movies.MovieFinder}={category=misc, service.id=22}
Registered by bundle: file:BasicMovieFinder.jar [4]
No bundles using service.
看到了,服務的確注冊了。在下節我們就講解如何查找這個服務并在另外模塊中使用此服務。
=============================================================================
OSGi開發起步(Getting Started with OSGi)-5使用一個服務
5. 使用一個服務:Consuming a Service
上節講解了如何注冊一個服務。現在就是在一個模塊中查找和使用這些服務的時候了。
與上節一樣,我們還是使用電影搜索的例子。我們已經構建了一個MovieFinder服務并在服務注冊表中進行了服務注冊。現狀我們需要構建一個MovieLister來使用這個MovieFinder服務搜索某個導演執導的影片。這里假設MovieLister也是一個服務,也可以被其他諸如GUI程序使用。問題是,OSGi服務是動態的:它們可能有效也可能處理不可使用狀態。這也就是說有時存在需要使用MovieFinder服務而它卻正好不能使用的情況。
那么當MovieFinder不可用的時候MovieLister怎么辦?顯然,調用MovieFinder是MovieLister能有效運作的核心部分。實際上我們也只有以下幾種選擇:
- 產生錯誤。如返回null或者拋出一個異常。
- 等待。
- 不讓這種情況出現。
在本文我們選擇比較簡單的前兩種選擇。The third option may not even make any sense to you yet, but hopefully it will after we look at some of the implications of the first two.
首先我們需要定義MovieLister服務的接口。復制以下內容到文件osgitut/movies/MovieLister.java:
package osgitut.movies;
import java.util.List;
public interface MovieLister { List listByDirector(String name); }
然后是文件osgitut/movies/impl/MovieListerImpl.java :
package osgitut.movies.impl;
import java.util.*;
import osgitut.movies.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
public class MovieListerImpl implements MovieLister {
private final ServiceTracker finderTrack;
public MovieListerImpl(ServiceTracker finderTrack) { this.finderTrack = finderTrack; }
public List listByDirector(String name)Unknown macro: { MovieFinder finder = (MovieFinder) finderTrack.getService(); if(finder == null) { return null; } else { return doSearch(name, finder); } }
private List doSearch(String name, MovieFinder finder) {
Movie[] movies = finder.findAll();
List result = new LinkedList();
for (int i = 0; i < movies.length; i++)Unknown macro: { if(movies[i].getDirector().indexOf(name) > -1) { result.add(movies[i]); } }
return result;
}
}
上面的代碼可能是至今我們最長的一段示例代碼了。這里面有些什么呢?第一,實際的影片搜索邏輯分離到了doSearch(string, MovieFinder)方法中。此方法幫助我們隔離了搜索代碼與OSGi代碼。雖然,這里制定的搜索辦法并不是很高效,但對于一個以示例為目的的程序已經足夠了:實際上只是實現了一個只有兩部影片的數據庫。
第二部分是listByDirector(String name)方法。這個方法使用了一個ServiceTracker對象從服務注冊表獲取一個MovieFinder。ServiceTracker是一個十分有用的類。它封裝和抽象了一些難于使用的OSGi底層API。無論如何我們還是要檢查需要的服務是否實際可用。此服務假定在MovieLister構造函數中把ServiceTracker傳遞過啦。
注意,你可能在其他部分看到一些沒有使用ServiceTracker從服務注冊表查詢服務的代碼。例如,使用BundleContext的getServiceReference和GetSevice方法。但這樣一來就需要編寫一些更復雜的代碼并需要仔細進行一些清理工作。在我看來,使用底層API并不能得到太多的好處,反倒是會引起許多問題。最好的選擇還是使用ServiceTracker。
在一個模塊的啟動器中是創建ServiceTracker的最佳地點。復制以下代碼到文件osgitut/movies/impl/MovieListerActivator.java:
package osgitut.movies.impl;
import java.util.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
import osgitut.movies.*;
public class MovieListerActivator implements BundleActivator {
private ServiceTracker finderTracker;
private ServiceRegistration listerReg;
public void start(BundleContext context) throws Exception { // Create and open the MovieFinder ServiceTracker finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null); finderTracker.open(); // Create the MovieLister and register as a service MovieLister lister = new MovieListerImpl(finderTracker); listerReg = context.registerService(MovieLister.class.getName(), lister, null); // Execute the sample search doSampleSearch(lister); }
public void stop(BundleContext context) throws Exception { // Unregister the MovieLister service listerReg.unregister(); // Close the MovieFinder ServiceTracker finderTracker.close(); }
private void doSampleSearch(MovieLister lister) {
List movies = lister.listByDirector("Miyazaki");
if(movies == null) { System.err.println("Could not retrieve movie list"); } elseUnknown macro: { for (Iterator it = movies.iterator(); it.hasNext();) { Movie movie = (Movie) it.next(); System.out.println("Title: " + movie.getTitle()); } }
}
}
現在這個啟動器可以啟動服務查找感興趣的影片了。首先,在start方法中創建了一個ServiceTracker對象。這個對象由之前寫的MovieLister使用。接著就打開ServiceTracker并設置此對象跟蹤注冊表中的MovieFinder服務實例。然后創建MovieListerImpl對象并使用MovieLister為名稱在注冊表中注冊一個服務。最后,為了證明服務確實運行,在啟動模塊時啟動器使用MovieLister執行了一個簡單的搜索并輸出了搜索結果。
到此,我們可以使用前幾節的方法構建和安裝我們設計的這些模塊。要記得創建元文件,并在元文件中正確的設置Bundle-Activator為osgitut.movies.impl.MovieListerActivator。還有就是在導入包中包含以下三個包:org.osgi.framework , org.osgi.util.tracker and osgitut.movies。
一旦把MovieLister.jar安裝到了OSGi框架運行環境,就可以啟動這個模塊。根據BasicMovieFinder模塊是否處于運行狀態,此時OSGi控制臺會輸出不同的消息。
如果沒有運行,會輸出:
osgi> start 2
Could not retrieve movie list
如果處于運行狀態則會輸出:
osgi> start 2
Title: Spirited Away
在停止和啟動模塊的時候,以上消息都會在控制臺出現。還記得我們可以在服務處于不可用狀態是選擇等待嗎?使用上面的代碼,只需要做簡單的修改:在MovieListerImpl的第16行中使用ServiceTracker的waitForService(5000)代替getService(),同時增加一個處理InterruptedException的try/catch塊。
這會導致listByDirector()方法阻塞5000毫秒等待MovieFinder服務可用。如果這時剛好MovieFinder服務安裝并且可以使用,這個方法就可以立即獲取到這個服務。
通常并不建議以這樣的方式掛起一個線程。在實踐中,這樣做是相對危險的。因為listByDirector方法實際是由模塊啟動器中的start方法調用的。而模塊啟動器start方法又是由一個框架線程調用。啟動器必須快速返回,因為當前一個模塊啟動是還需要執行許多其他的工作。實際上在最糟糕的情況下這種等待的做法可能導致一個死鎖。因為我們進入了一個框架對象的同步區,而這個同步區可能已經被其他現場鎖定。通常的建議是不要在模塊啟動器的start方法或者其他任何框架直接調用的代碼中執行任何長時間或者阻斷性的操作。
=================================================================
OSGi開發起步(Getting Started with OSGi)-6(1)動態服務跟蹤
6 動態服務跟蹤:Dynamic Service Tracking
上節講解了如何使用一個服務:MovieLister使用MovieFinder查找由某個導演指導的影片。同時還涉及了處理OSGi服務動態特性的各種策略,特別介紹了MovieLister找不到一個有效的MovieFinder服務時的處理辦法。
還有一種上節沒有涉及的情況是:如何處理同時有多個MovieFinder服務可用的情況?因為任何模塊都可以使用MovieFinder接口注冊一個服務,并且對于服務注冊機制來說任何模塊都是同等對待的。
我們可以簡單的忽略這個問題,這也是上節代碼的實際處理邏輯。通過調用ServiceTracker上的getService()方法,我們選擇由服務注冊表任意提供一個MovieFinder服務。有一些參數可以調整注冊表的選擇策略(比如在服務注冊時設置的SERVICE_RANKKING屬性),但是作為一個服務的使用者我們并沒有參與這個服務選擇過程。并且實際上盡量少的控制并不是一件壞事,因為我們應該可以使用任何一個MovieFinder服務。這也是使用接口的原因。
從另外一方面來說,在一些情況下注冊和使用多個服務也大有用途,例如,如果有多個有效的MovieFinder服務,這就意味這存在多個影片數據來源可供MovieLister使用。如果可以通過使用所有的這些服務進行影片查找,我們就可以獲得更大的搜索網絡空間并提供給用戶更好的搜索結果。
另一個問題是上節遺留下來的:在沒有任何一個MovieFinder服務可用時,MovieLister應該如何正確處理?上節的處理邏輯是在服務無效時簡單的在listByDirector調用時返回null。But what if we made it impossible for methods on MovieLister to be called when the MovieFinder isn't present?
MovieLister與MovieFinder一樣是一個服務。如果MovieFinder服務消失了,MovieLister服務是不是也應該消失?也就是說,我們希望MovieLister與MovieFinder之間有一個一對多的依賴關系。本文的最后一節,我們介紹零對一依賴關系。
首先使用下面的代碼修改MovieListerImpl類:
package osgitut.movies.impl;
import java.util.*;
import osgitut.movies.*;
public class MovieListerImpl implements MovieLister {
private Collection finders =
Collections.synchronizedCollection(new ArrayList());
protected void bindFinder(MovieFinder finder) { finders.add(finder); System.out.println("MovieLister: added a finder"); }
protected void unbindFinder(MovieFinder finder) { finders.remove(finder); System.out.println("MovieLister: removed a finder"); }
public List listByDirector(String director) {
MovieFinder[] finderArray = (MovieFinder[])
finders.toArray(new MovieFinder[finders.size()]);
List result = new LinkedList();
for(int j=0; j<finderArray.length; j++) {
Movie[] all = finderArray[j].findAll();
for(int i=0; i<all.length; i++) {
if(director.equals(all[i].getDirector())) { result.add(all[i]); }
}
}
return result;
}
}
上面的代碼移除了MovieListerImpl上所有的OSGi依賴。MovieListerImpl現在是一個純POJO了。當然,它還需要其他對象協助跟蹤MovieFinder服務并通過bindFinder方法提供此服務。因此我們還需要創建一個新的java文件:osgitut/movies/impl/MovieFinderTracker.java。
package osgitut.movies.impl;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import osgitut.movies.*;
public class MovieFinderTracker extends ServiceTracker {
private final MovieListerImpl lister = new MovieListerImpl();
private int finderCount = 0;
private ServiceRegistration registration = null;
public MovieFinderTracker(BundleContext context) { super(context, MovieFinder.class.getName(), null); }
private boolean registering = false;
public Object addingService(ServiceReference reference) {
MovieFinder finder = (MovieFinder) context.getService(reference);
lister.bindFinder(finder);
synchronized(this) { finderCount ++; if (registering) return finder; registering = (finderCount == 1); if (!registering) return finder; }
ServiceRegistration reg = context.registerService(
MovieLister.class.getName(), lister, null);
synchronized(this) { registering = false; registration = reg; }
return finder;
}
public void removedService(ServiceReference reference, Object service) {
MovieFinder finder = (MovieFinder) service;
lister.unbindFinder(finder);
context.ungetService(reference);
ServiceRegistration needsUnregistration = null;
synchronized(this) {
finderCount --;
if (finderCount == 0) { needsUnregistration = registration; registration = null; }
}
if(needsUnregistration != null) { needsUnregistration.unregister(); }
}
}
==========================================================================================
OSGi開發起步(Getting Started with OSGi)-6(2)動態服務跟蹤
上面這個類覆蓋了上節討論的ServiceTracker類,并且定制了服務有效和失效時ServiceTracker的行為。具體來說就是,當一個增加MovieFinder服務時調用addingSevice方法,當刪除一個MovieFinder服務時調用removedService方法。此外還有一個modifiedService方法,只不過在本節處理邏輯中不需要而沒有覆蓋。
有必要仔細看看這兩個方法中的處理邏輯。首先,在addingSevice方法中傳入的參數是ServiceReference而不是一個實際服務實現對象。ServiceReference是一個輕量級的句柄對象。它可以作為一個參數任意的進行傳輸,并可以用來獲取實際服務的屬性。比如,服務在進行服務注冊時傳入的屬性集。實際上,使用一個ServiceReference對象并不會導致OSGi框架增加實際服務的引用計數。你可以認為ServiceReference承擔了類似java反射API中WeakReference類的角色。
在addingSevice方法中首先是通過ServiceReference獲取一個實際的MovieFinder服務對象。這又要使用BundleContext:任何與OSGi框架的交互都是通過BundleContext接口進行的。幸運的是,ServiceTracker定義了一個我們可以直接使用的類型為BundleContext的保護成員context。
接下來我們使用bindFinder方法把獲取到的MovieFinder服務綁定到MovieListerImpl。然后就把MovieListerImpl也注冊為一個使用MovieLister接口的服務。注意,我們只在MovieListerImpl沒有注冊為服務的情況下才進行服務注冊。這是因為,我們現在希望一個MovieLister使用多個MovieFinder服務。
最后,addingSevice方法返回一個Object。問題是,我們到底要返回一個什么對象呢?實際上,ServiceTracker并不關心addingSevice返回什么對象。addingSevice可以返回任何我們需要的對象。重點是,addingSevice返回的對象會在調用modifiedService或者removedService的時候重新傳回。這也就是在removedService方法第一行見到的直接對object進行的MovieFinder類型轉換。接著就使用此對象進行它與MovieLister的解綁,同時我們根據可以使用的MovieFinder服務是否為零進行MovieLister服務的反注冊。
一般說來,任何在addingSevice中的行為都應該在removedService中進行狀態清理。因此,我們應該在addingSevice中返回任何有助于我們需要在removedService中進行清理工作的對象。返回的可以是一個HashMap中的鍵值,也可以是一個ServiceRegistration對象,或者是一個實際的服務對象。
在removedService的最后一步,我們解綁了在addingSevice中綁定的服務。這一點十分重要,這個操作使得服務注冊表減少服務使用計數值,從而可以使計數值為零進行服務釋放。
現狀我們還需要一個模塊啟動器osgitut/movies/impl/TrackingMovieListerActivator.java:
package osgitut.movies.impl;
import org.osgi.framework.*;
public class TrackingMovieListerActivator implements BundleActivator {
private MovieFinderTracker tracker;
public void start(BundleContext context) { tracker = new MovieFinderTracker(context); tracker.open(); }
public void stop(BundleContext context) { tracker.close(); }
}
不要忘記TrackingMovieLister.mf:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tracking Movie Lister
Bundle-SymbolicName: TrackingMovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.TrackingMovieListerActivator
Import-Package: org.osgi.framework,
org.osgi.util.tracker,
osgitut.movies;version="[1.0.0,2.0.0)"
按照前幾節的步驟進行模塊的構建和部署。完成后,執行以下的步驟驗證運行邏輯是否正確:
- 啟動 BasicMovieFinder和TrackingMovieLister。檢查是否出現"MovieLister:added a finder"消息。
- 停止 BasicMovieFinder。檢查是否出現"MovieLister: removed a finder"消息。
- 執行service命令,檢查是否MovieLister服務已經反注冊。
本節內容展示的是一個十分強大的技術。我們把一個服務的生命周期與另一個服務(實際上是多個)的生命周期綁定。進一步,我們可以綁定MovieLister和第三個服務并可以依此類推。這樣我們就構建了一個服務依賴圖,這種依賴圖與其他一些靜態IOC容器構建的beans圖不同。OSGi的服務依賴圖更健壯,self-healing并具有自動調整能力。
另一方面,這一節的代碼還存在一些問題。MovieFinderTracker和TrackingMovieListerActivator類會形成整個系統負載的瓶頸。當我們打算進行這個系統的擴展時,我們還不得不重復進行這些基本雷同的編碼工作。因此在下一節我們講解如何使用幾行XML語句完成這些重復和繁瑣的工作。
在下一節不再在一個目錄下使用命令行構建我們的系統。本文的目標是展示OSGi是一個簡單而強大的框架,但并不妨礙我們使用更高效的方式比如eclipse的IDE完成這些工作。
==================================================================================================
OSGi開發起步(Getting Started with OSGi)-7服務聲明介紹
7 服務聲明介紹:Introducing Declarative Services
服務聲明(簡稱DS)規范是OSGi最新的內容之一。增加此服務規范是為了在多個模塊之間有效協調服務。這種服務協調工作本身并不復雜(如上節我們展示的那樣),但是這項工作總是面臨那些惱人的代碼。而且具體實現時還必須注意處理各種線程問題。總的來說,模塊服務協作這件工作是一件極易出錯的工作。
最早試圖解決服務協作問題的是一個稱為Service Binder的工具。這個工具由Humberto Cervantes 和 Richard Hall開發。Service Binder設計了自動服務依賴管理特性。這個特性允許開發者專注于服務。服務之間的協作和依賴通過聲明實現,而所有的聲明都使用XML完成。
服務聲明規范是由Service Binder發展而來,并成為了OSGi4.0標準的一部分。
前面幾節的示例都是使用的命令行完成所有的編碼、配置、構建和運行工作。從這節開始則使用EclipseSDK。需要明確的是,本文中介紹的任何技術和方法并不依賴于Eclipse。盡管Eclipse可以很好的幫助我們完成很多工作,但同樣的我們也可以使用NetBeans,Intellij或者vi來完成這些工作。
首先我們需要下載Equinox的服務聲明實現包。可以在Equinox的下載頁面下載最新的org.eclipse.equniox.ds_x.x.x_xxxx.jar。下載完成后,把下載的jar復制到Eclipse的plugins目錄,并重新啟動Eclipse。
現在重新創建一個模塊。在Eclipse中使用插件工程向導:
l 在主菜單中選擇File->New->Project。
l 選擇Plug-in Project并點擊Next。
l 輸入工程的名稱:SampleExporter。
l 在最下面的"This plug-in is targeted to run with"文字下選擇"OSGi framework"以及下拉框中的"standard"。這個是個基本步驟:這一步可以防止我們使用OSGi框架實現之外的特性。
l 選擇"Next"。在向導顯示的下一個對話框中選擇"Generate an activator..."。如果選擇,點擊"Finish"完成創建工作。
我們現在生成了一個空的模塊工程。接下來的工作就是在這個工程中增加代碼。為了使得示例盡量簡單,我們使用在每個java運行環境中都存在的java.lang.Runnable接口提供服務。現在我們可以使用Eclipse的復制代碼特性創建包和源文件。復制下面的代碼,在Eclipse中選擇SampleExporter工程的src目錄,并執行Edit->Paste。
package org.example.ds;
public class SampleRunnable implements Runnable {
public void run() { System.out.println("Hello from SampleRunnable"); }
}
接下來我們需要創建一個XML文件。使用這個文件聲明SampleRunnable是一個服務。在工程的頂級目錄下創建一個名稱為OSGI-INF的目錄,新建一個名稱為samplerrunnable.xml的文件然后復制下面的文本到文件中。
<?xml version="1.0"?>
<component name="samplerunnable">
<implementation class="org.example.ds.SampleRunnable"/>
<service>
<provide interface="java.lang.Runnable"/>
</service>
</component>
這是一個最簡單的DS聲明。這個聲明表示有一個名稱為"samplerunnable"的組件,這個組件使用接口java.lang.Runnable向服務注冊器提供一個服務。同時還說明了這個模塊是由org.example.da.SampleRunnable類實現的。
最后就是要告知服務聲明runtime這個XML文件的位置。通知是通過在組件的MANIFEST.MF文件中增加一項說明實現的。在Eclipse中打開元文件頁面,定位到"MANIFEST.MF"文件窗格。在這個文件內容的最后增加一行:
Sevice-Component: OSGI-INF/samplerunnable.xml
然后保存文件。在進行下一步之前,我們可以運行Equinox檢查一下我們的模塊是否可以正常運行。在Run菜單中選擇"Run...",當運行對話框打開后選擇左邊樹中的"Equniox OSGi Framework"并點擊New按鈕。如果你是一個熟練的Eclipse用戶但又沒有做過OSGi開發,這個對話框相對來說有些復雜(或者奇怪)。第一個標識為Bundles的窗格允許我們選擇哪些Bundles是運行環境需要包含的,是否需要啟動等。為了使用一個簡化的運行環境,取消當前所有選中的Bundles,并重新選擇以下的Bundles:
SampleExporter (underneath Workspace)
org.eclipse.equinox.ds (underneath Target Platform)
除此之外我們還有一些依賴的Bundles,這可以使用Add Required Bundles功能把這些依賴的Bundles添加進來。最好把"Validate bundles automatically prior to launching"也選中。最后,點擊Run運行這個工程。一段時間后osgi>提示符就出現在Eclipse控制臺。OSGi框架運行起來了,我們可以使用前幾節的命令與框架進行交互。
我們開發的服務注冊了沒有?使用services命令檢查一下。在命令的輸出結果中,我們會發現類似的信息:
{java.lang.Runnable}={component.name=samplerunnable, component.id=1, service.id=22}
Registered by bundle: initial@reference:file:..../SampleExporter/ [5]
No bundles using service.
看來服務的確注冊了。需要指出的是:服務是由我們的模塊注冊的,而不是由聲明服務模塊注冊的,實際上,In fact DS has registered it on behalf of our bundle。還需要注意的是,服務的使用者并不需要為了使用這個服務而做什么特別的處理,它甚至不必知道我們使用了服務聲明。這些服務使用者也可以使用DS或者直接使用OSGi代碼來訪問服務。
還有一點就是在上面的模塊代碼中并沒有出現與OSGi標準相關的java代碼。也就是說,本節我們編寫的是一個POJO類。這也是DS的一個主要特點。
=================================================================================================================
OSGi開發起步(Getting Started with OSGi)-8(1)服務聲明和依賴
8服務聲明和依賴:Declarative Services and Dependencies
上節對DS做了初步的介紹。本節介紹如何使用DS聲明的服務。上節我們使用java.lang.Runnable接口使用DS注冊了一個服務,本節介紹如何創建一個依賴于這個服務的組件。
正如介紹的那樣,DS規范使得開發者只需關注應用自身的邏輯,避免如前幾節需要編寫的那些OSGi服務"粘合"代碼。使用DS,我們只要簡單的編寫代碼就可以了。但在之間編碼之前,我們還得創建一個Eclipse工程。使用上節的工程創建步驟創建一個名稱為SampleImporter的工程。
復制下面的代碼并粘貼到新建工程的src目錄:
package org.example.ds;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
public class SampleCommandProvider1 implements CommandProvider {
private Runnable runnable;
public synchronized void setRunnable(Runnable r) { runnable = r; }
public synchronized void unsetRunnable(Runnable r) { runnable = null; }
public synchronized void _run(CommandInterpreter ci) {
if(runnable != null) { runnable.run(); } else { ci.println("Error, no Runnable available"); }
}
public String getHelp() { return "\trun - execute a Runnable service"; }
}
這個類實現了CommandProvider接口。啟動Equinox后在osgi>提示符可以執行一系列的命令。CommandProvider接口用于擴充這些命令。編寫CommandProvider的原因是為了提供一種方便的交互式測試代碼方式。在IBM developerWorks上Chirs Aniszczyk的一篇文章詳細討論了CommandProvider。
在上面的代碼中我們注意到沒有任何OSGi方法調用,實際上我們并不需要從org.osgi.*導入任何類。我們依賴的那個服務(在本示例中是一個java.lang.Runnable實例)是通過setRunnable方法提供的,并且通過unsetRunnable移除。我們可以認為這是某種形式的依賴注射。
另外兩個方法getHelp和_run是CommandProvider接口定義的實現方法。_run方法名的下劃線有些滑稽,但這也只是Equinox控制臺API的一項odd特性,與OSGi的DS無關。在Equinox控制臺中規定具有下劃線的方法名稱是一個控制臺命令。因此定義一個_run方法就相當于在Equinox控制臺中增加了一個run命令。另外一個需要注意的情況是,在上面這個類的實現代碼中仔細的處理了runnable屬性的線程安全特性。由于OSGi本質上是多線程的,因此在OSGi中線程安全是十分重要的。Frankly我們應該總是編寫線程安全的代碼。
和以前一樣,我們還是需要一個包含DS聲明的XML文件。復制下面的文本到這個插件工程的OSGI-INF/commandprovider1.xml文件中:
<?xml version="1.0"?>
<component name="commandprovider1">
<implementation class="org.example.ds.SampleCommandProvider1"/>
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference name="RUNNABLE"
interface="java.lang.Runnable"
bind="setRunnable"
unbind="unsetRunnable"
cardinality="0..1"
policy="dynamic"/>
</component>
編寫完成了XML文件后還要把下面這行加入到bundle元文件的最后:
Service-Component: OSGI-INF/commandprovider1.xml
===========================================================================================================
OSGi開發起步(Getting Started with OSGi)-8(2)服務聲明和依賴
在XML文件中有兩個我們已經介紹過的元素項:implementation和service。Implementation提供了組件的實現類,而service則告知DS把這個組件注冊為一個服務。這個示例中的服務使用CommandProvider接口注冊。這個接口使得Equinox控制臺知曉這個CommandProvider的存在。
文件中還有一個以前沒有介紹的reference元素項。reference用于向DS聲明組件與一個服務之間的依賴關系。其中的name屬性為任意字符串。名稱屬性用于對依賴進行命名(我們并不需要關心它的用途-實際上只是一個可讀性的標識)。本示例中使用了組件依賴的接口名稱來命名。bind屬性指定一個實現類的方法名。當一個服務可用時,也就是一個Runnable的服務在服務注冊表中注冊時,DS就會調用此方法。DS使用這個方法傳入這個新服務對象的引用提供給我們的組件使用。同樣的,unbind屬性是當一個服務不可用時,DS回調的方法。
Cardinality是一個能顯示DS真正能力的屬性。這個屬性控制服務的依賴是可選的還是強制的、單依賴還是多依賴。這個屬性可能的值為:
0..1: optional and singular, "zero or one"
1..1: mandatory and singular, "exactly one"
0..n: optional and multiple, "zero to many"
1..n: mandatory and multiple, "one to many" or "at least one"
在這個示例中我們使用的是0.1。也就是能cope那些不可用的服務。回頭看以下_run方法中的代碼,我們就會發現為了處理這種情況而進行的null檢查代碼。
現在還是來看看這個模塊運行的情況。如果上節安裝的SampleExporter還有效,在osgi控制臺輸入run命令會得到如下輸出:
Hello from SampleRunnable
這證實了我們已經成功將上節編寫的Runnable服務成功導入。接著運行stop命令停止SampleExporter模塊。再運行run命令得到的輸出是:
Error,no Runnable available
這個輸出表明DS發現了Runnable服務已經停止并調用了unsetRunnable方法。
再看一下cardinality屬性,如果把它的值改為1.1(改可選為強制)會怎樣?更改這個屬性后重新運行Equinox。在SampleExporter啟動后運行run命令會得到與前面相同的結果。但是當停止SampleExporter后運行run,OSGi輸出的是一個與前面不同的錯誤消息。實際上,Equinox控制臺輸出的是一個幫助消息。這個消息是對一個不可識別命令的標準錯誤輸出消息。這也就是說我們這個命令執行服務已經被DS反注冊了。當一個組件的強制依賴不能滿足是,DS強制停止這個組件并反注冊組件提供的任何服務。這就是Equinox不能識別run命令的原因。
如此方便的服務裝配是選擇DS的最佳原因。還記得使用ServiceTracker時,我們處理這種情況時不得不編寫的那些代碼嗎?
還有一個值得關注的屬性是ploicy。這個屬性的值定義為static和dynamic之一。這兩個狀態值用于表明組件是否支持服務的動態切換。如果不支持,DS就沒有必要每次在目標服務變化后停止原組件并創建一個新組件實例。停止后新建這個過程可是一個重負載性的過程,所以我們的建議最好還是盡可能的編碼一個支持動態切換的組件。不過DS的這個屬性缺省是靜態的,因此在組件開發時還得手動的調整此屬性為動態。
上面我們試了0.1和1.1,但是沒有講解多對多(0.n)的依賴。在服務注冊表中不會僅僅只有一個Runnable服務注冊。如果我們只是需要綁定其中的一個服務,那么我們所綁定的將是其中的任意一個服務。看來我們還需要一個runall命令用來運行服務注冊表中已經注冊的所有Runnable服務。
但就對上面那個組件來說,我們把cardinality屬性更改為"0.n"時會出現什么情況?其實,更改后這個服務還是可以運行:只不過setRunnable不會只調用一次,DS會在每個Runnable服務注冊是調用此方法一次。問題是多次調用會導致上面組件處理邏輯的混亂。因此為應對更通常的0.n的情況,就不能只是使用單個的Runnable成員,而是需要一個Runnable集合成員。下面是根據以上分析修改的類,把這個類的代碼復制到工程的src目錄下:
package org.example.ds;
import java.util.*;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
public class SampleCommandProvider2 implements CommandProvider {
private List<Runnable> runnables =
Collections.synchronizedList(new ArrayList<Runnable>());
public void addRunnable(Runnable r) { runnables.add(r); }
public void removeRunnable(Runnable r) { runnables.remove(r); }
public void _runall(CommandInterpreter ci) {
synchronized(runnables) {
for(Runnable r : runnables) { r.run(); }
}
}
public String getHelp() { return "\trunall - Run all registered Runnables"; }
}
再創建OSGI-INF/commandprovider2.xml:
<?xml version="1.0"?>
<component name="commandprovider2">
<implementation class="org.example.ds.SampleCommandProvider2"/>
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference name="RUNNABLE"
interface="java.lang.Runnable"
bind="addRunnable"
unbind="removeRunnable"
cardinality="0..n"
policy="dynamic"/>
</component>
最后是把這個文件按照如下方式加到元文件中:
Service-Component: OSGI-INF/commandprovider1.xml,
OSGI-INF/commandprovider2.xml
上面的XML文件中的DS聲明與以前的基本一樣,只是修改了bind和unbind方法的名稱,以及cardinality的值(0.n)。現在就可以試著運行這個新的runall命令看看結果如何啦。欣賞完輸出結果后,還可以試著更改cardinality為1.n看看有什么不一樣的事情發生!
本節是這個講解的最后一節。通過這幾節的講解我們發現OSGi并不神秘并且相當易用。接下來的事情就是不斷的開發和使用它。不過,我還是強烈的建議在開始和進行過程中一定認真的學習一下OSGi R4 core和service規范。
=======================================================================================