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