Java動(dòng)態(tài)模塊化運(yùn)行原理與實(shí)踐
我們之前曾了解過面向Java EE 6平臺(tái)的上下文和依賴性注入和OSGi依賴性管理,比如Bundle的訪問域等內(nèi)容。其實(shí),標(biāo)準(zhǔn)Java代碼和模塊化Java代碼的區(qū)別之一就是依賴在運(yùn)行時(shí)是如何綁定的。在本篇文章中,我們將詳細(xì)討論模塊化Java中的動(dòng)態(tài)模塊化,包括對(duì)Bundle ClassPath、類的垃圾回收以及查找綁定等。
Bundle ClassPath
對(duì)于一個(gè)普通Java程序,只有一個(gè)classpath——啟動(dòng)應(yīng)用程序所使用的那個(gè)。該路徑通常是在命令行中用-classpath選項(xiàng)指定的,或者通 過CLASSPATH 環(huán)境變量來(lái)設(shè)定。Java類裝載器在運(yùn)行時(shí)解析類的時(shí)候會(huì)掃描此路徑,無(wú)論這一過程是靜態(tài)地(已編譯進(jìn)代碼)還是動(dòng)態(tài)地(使用反射及 class.forName())。然而,在運(yùn)行時(shí)也可以使用多個(gè)類加載器;像Jetty和Tomcat這樣的Web應(yīng)用引擎都是使用多個(gè)類加載器,以便支持應(yīng)用熱部署。
在OSGi中,每個(gè)bundle都有其自己的類加載器。需要被其他bundle訪問的類則被委派(delegated)給這些其他bundle的類裝載器。因此,盡管在傳統(tǒng)應(yīng)用中,來(lái)自logging類庫(kù)、client和server JAR中的類都是由同一個(gè)類加載器加載的,但在OSGi模塊系統(tǒng)中,他們都是由自己的類加載器加載的。
結(jié)果是,一個(gè)VM中有可能有多個(gè)類加載器,其中可能存在名字相同的不同Class的對(duì)象。也就是說,在同一個(gè)VM中,一個(gè)叫做 com.infoq.example.App的類,其不同版本可以由com.infoq.example bundle的第1版和第2版同時(shí)輸出。Client bundle版本1使用該類的第1版,而client版本2使用該類的第2版。這在模塊化系統(tǒng)中相當(dāng)普遍;在同一個(gè)VM中,有些代碼可能需要裝載一個(gè)類庫(kù) 的老版本,同時(shí)更新點(diǎn)的代碼(在另一個(gè)bundle中)卻需要該類庫(kù)的新版本。好在OSGi為你管理起這種依賴傳遞,確保不再出現(xiàn)不兼容類引發(fā)的問題。
類的垃圾回收
每個(gè)類都有一個(gè)對(duì)其類裝載器的引用。因此如果想要從不同的bundle訪問這些類,不但要有對(duì)該類實(shí)例的引用,而且還要有對(duì)該類的類裝載器的引用。當(dāng)一個(gè)bundle持有另一個(gè)bundle的類時(shí),它也會(huì)將該bundle固定在內(nèi)存中。在前篇文章的例子中,client被固定到該server上。
#T#在靜態(tài)世界里,無(wú)論你是否把自己的類固定到其他類(或類庫(kù))都無(wú)所謂;因?yàn)椴粫?huì)有什么變化??墒?,在動(dòng)態(tài)世界里,在運(yùn)行時(shí)將類庫(kù)或工具替換成新版本就有可 能了。這聽起來(lái)可能有點(diǎn)復(fù)雜,但是在可熱部署應(yīng)用的Web應(yīng)用引擎早期就出現(xiàn)了(如Tomcat,最早發(fā)布于1999年)。每個(gè)Web應(yīng)用程序都綁定到 Servlet API的某個(gè)版本上,當(dāng)其停止時(shí),裝載該Web應(yīng)用的類加載器也就廢棄掉了。當(dāng)Web應(yīng)用重新被部署時(shí),又創(chuàng)建了一個(gè)新的類加載器,新版類就由其裝載。只要servlet引擎沒有保持對(duì)老版應(yīng)用的引用,這些類就像其他Java對(duì)象一樣被垃圾回收器回收了。
并不是所有的類庫(kù)都能意識(shí)到Java代碼中可能存在類泄漏的問題,就像是內(nèi)存泄漏。一個(gè)典型的例子就是Log4J的addAppender()調(diào)用,一旦其執(zhí)行了,將會(huì)把你的類綁定在Log4J bundle的生命周期上。即使你的bundle停止了,Log4J仍將維對(duì)appender的引用,并繼續(xù)發(fā)送日志事件(除非該bundle在停止時(shí)恰當(dāng)?shù)卣{(diào)用了removeAppender()方法)。
查找和綁定
為了成為動(dòng)態(tài),我們需要有一個(gè)能查找服務(wù)的機(jī)制,而不是持久持有他們(以免bundle停止)。這是通過使用簡(jiǎn)單Java接口和POJO來(lái)實(shí)現(xiàn)的,也就是大家所熟知的services(注意他們與WS-DeathStar或其他任何XML底層架構(gòu)都沒有關(guān)系;他們就是普通Java對(duì)象——Plain Old Java Objects)。
典型工廠實(shí)現(xiàn)方式是使用從properties文件中獲取的某種形式的類名,然后用Class.forName()來(lái)實(shí)例化相應(yīng)的類,OSGi則不同,它 維護(hù)了一個(gè)‘服務(wù)注冊(cè)器’,其實(shí)這是一個(gè)包含了類名和服務(wù)的映射列表。這樣,OSGi系統(tǒng)就可以使用 context.getService(getServiceReference("java.sql.Driver")),而不是 class.forName("com.example.JDBCDriver")來(lái)獲取一個(gè)JDBC驅(qū)動(dòng)器。這就把client代碼解放出來(lái)了,它不需 知道任何特定客戶端實(shí)現(xiàn);相反,它可以在運(yùn)行時(shí)綁定任何可用驅(qū)動(dòng)程序。移植到不同的數(shù)據(jù)庫(kù)服務(wù)器也就非常簡(jiǎn)單了,只需停止一個(gè)模塊并啟動(dòng)一個(gè)新模 塊;client甚至不需要重新啟動(dòng),也不需要改變?nèi)魏闻渲谩?/p>
這樣做是因?yàn)閏lient只需知道其所需的服務(wù)的API(基本上都是接口,盡管OSGi規(guī)范允許使用其他類)。在上述情況中,接口名是 java.sql.Driver;返回的接口實(shí)例是具體的數(shù)據(jù)庫(kù)實(shí)現(xiàn)(不必了解是哪些類,編碼在那里)。此外,如果服務(wù)不可用(數(shù)據(jù)庫(kù)不存在,或數(shù)據(jù)庫(kù)臨 時(shí)停掉了),那么這個(gè)方法會(huì)返回null以說明該服務(wù)不可用。
為了完全動(dòng)態(tài),返回結(jié)果不應(yīng)被緩存。換句話說,每當(dāng)需要服務(wù)的時(shí)候,需要重新調(diào)用getService??蚣軙?huì)在底層執(zhí)行緩存操作,因此不存在太大的性能 問題。但重要的是,它允許數(shù)據(jù)庫(kù)服務(wù)在線被替換成新的服務(wù),如果沒有緩存代碼,那么下次調(diào)用時(shí),client將透明地綁定到新服務(wù)上。
付諸實(shí)施
為了證明這一點(diǎn),我們將創(chuàng)建一個(gè)用于縮寫URL的OSGi服務(wù)。其思路是服務(wù)接收一個(gè)長(zhǎng)URL,如http://www.example.com/articles/modular-java-what-is-it,將其轉(zhuǎn)換為短點(diǎn)的URL,如http://tr.im/EyH1。該服務(wù)也可以被廣泛應(yīng)用在Twitter這樣的站點(diǎn)上,還可以用它來(lái)把長(zhǎng)URL轉(zhuǎn)成短的這樣便簽背后也寫得下。甚至像《新科學(xué)家》和《Macworld》這樣的雜志也是用這些短URL來(lái)印刷媒體鏈接的。#p#
為了實(shí)現(xiàn)該服務(wù),我們需要:
◆一個(gè)縮寫服務(wù)的接口
◆一個(gè)注冊(cè)為縮寫實(shí)現(xiàn)的bundle
◆一個(gè)驗(yàn)證用client
盡管并沒有禁止把這些東西都放在同一個(gè)bundle中,但是我們還是把他們分別放在不同的bundle里。(即便他們?cè)谝粋€(gè)bundle中,最好也讓bundle通過服務(wù)來(lái)通訊,就好像他們處于不同的bundle一樣;這樣他們就可以方便地與其他服務(wù)提供者進(jìn)行集成。
把縮寫服務(wù)接口與其實(shí)現(xiàn)(或client)分開放在單獨(dú)bundle中是很重要的。該接口代表了client和server之間的‘共享代碼’,這樣,該 接口在每個(gè)bundle中都會(huì)加載。正因如此,每個(gè)bundle實(shí)際上都被固定到了該接口特定版本上,所有服務(wù)都有共同的生命周期,將接口放在單獨(dú) bundle中(在整個(gè)OSGi VM生命周期中都在運(yùn)行),我們的client就可以自由變化。如果我們把該接口放在某個(gè)服務(wù)實(shí)現(xiàn)的bundle中,那么該服務(wù)發(fā)生變化后我們就不能重新 連接到client上了。
shorten接口的manifest和實(shí)現(xiàn)如下:
- Bundle-ManifestVersion: 2
- Bundle-Name: Shorten
- Bundle-SymbolicName: com.infoq.shorten
- Bundle-Version: 1.0.0
- Export-Package: com.infoq.shorten
- ---
- package com.infoq.shorten;
- public interface IShorten {
- public String shorten(String url) throws IOException;
- }
上面的例子建立了一個(gè)擁有單一接口(com.infoq.shorten.IShorten)的bundle(com.infoq.shorten),并將其輸出給client。參數(shù)是一個(gè)URL,返回一個(gè)唯一的壓縮版URL。
和接口定義相比,實(shí)現(xiàn)就相對(duì)有趣一些了。盡管最近縮寫名稱的應(yīng)用開始多起來(lái)了,但是所有這些應(yīng)用的祖師爺都是 TinyURL.com。(具有諷刺意味的是,http://tinyurl.com實(shí)際上可以被壓縮的更短http://ow.ly/AvnC)。如今比較流行有:ow.ly、bit.ly、tr.im等等。這里并不是對(duì)這些服務(wù)全面介紹,也不是為其背書,我們的實(shí)現(xiàn)也可以使用其他同類服務(wù)。本文之所以使用TinyURL和Tr.im,是由于他們都可以匿名基于GET提交,易于實(shí)現(xiàn),除此之外沒有其他原因。
每種實(shí)現(xiàn)實(shí)際上都非常??;都以URL為參數(shù)(要縮寫的東西)并返回新的壓縮過的文本:
- package com.infoq.shorten.tinyurl;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.net.URL;
- import com.infoq.shorten.IShorten;
- public class TinyURL implements IShorten {
- private static final String lookup =
- "http://tinyurl.com/api-create.php?url=";
- public String shorten(String url) throws IOException {
- String line = new BufferedReader(
- new InputStreamReader(
- new URL(lookup + url).openStream())).readLine();
- if(line == null)
- throw new IllegalArgumentException(
- "Could not shorten " + url);
- return line;
- }
- }
Tr.im的實(shí)現(xiàn)類似,只需用http://api.tr.im/v1/trim_simple?url=替代lookup的值即可。這兩種實(shí)現(xiàn)的源代碼分別在com.infoq.shorten.tinyurl和com.infoq.shorten.trim bundle里。
那么,完成縮寫服務(wù)的實(shí)現(xiàn)后,我們?nèi)绾巫屍渌绦蛟L問它呢?為此,我們需要把實(shí)現(xiàn)注冊(cè)為OSGi框架的服務(wù)。BundleContext類的registerService(class,instance,properties)方法可以讓我們定義一個(gè)服務(wù)以供后用,該方法通常在bundle的start()調(diào)用期間被調(diào)用。如上篇文章所講,我們必須定義一個(gè)BundleActivator。實(shí)現(xiàn)該類后,我們還要把Bundle-Activator放在MANIFEST.MF里以便找到該實(shí)現(xiàn)。代碼如下:
- Manifest-Version: 1.0
- Bundle-ManifestVersion: 2
- Bundle-Name: TinyURL
- Bundle-SymbolicName: com.infoq.shorten.tinyurl
- Bundle-Version: 1.0.0
- Import-Package: com.infoq.shorten,org.osgi.framework
- Bundle-Activator: com.infoq.shorten.tinyurl.Activator
- ---
- package com.infoq.shorten.tinyurl;
- import org.osgi.framework.BundleActivator;
- import org.osgi.framework.BundleContext;
- import com.infoq.shorten.IShorten;
- public class Activator implements BundleActivator {
- public void start(BundleContext context) {
- context.registerService(IShorten.class.getName(),
- new TinyURL(),null);
- }
- public void stop(BundleContext context) {
- }
- }
盡管registerService()方法接收一個(gè)字符串作為其第一個(gè)參數(shù),而且用"com.infoq.shorten.IShorten"也是可以的,但是最好還是用class.class.getName()這種形式,這樣如果你重構(gòu)了包或改變了類名,在編譯時(shí)就可發(fā)現(xiàn)問題。如果用字符串,進(jìn)行了錯(cuò)誤的重構(gòu),那么只有在運(yùn)行時(shí)你才能知道問題所在。
registerService()的第二個(gè)參數(shù)是實(shí)例本身。之所以將其與第一個(gè)參數(shù)分開,是因?yàn)槟憧梢詫⑼粋€(gè)服務(wù)實(shí)例輸出給多個(gè)服務(wù)接口(如果需要帶有版本的API,這就有用了,你可以進(jìn)化接口了)。另外,一個(gè)bundle輸出同一類型的多個(gè)服務(wù)也是有可能的。
最后一個(gè)參數(shù)是服務(wù)屬性(service properties)。允許你給服務(wù)加上額外元數(shù)據(jù)注解,比如標(biāo)注優(yōu)先級(jí)以表明該服務(wù)相對(duì)于其他服務(wù)的重要性,或者調(diào)用者關(guān)心的其他信息(比如功能描述和廠商)。
只要該bundle一啟動(dòng),縮寫服務(wù)就可用了。當(dāng)bundle停止,框架將自動(dòng)取消服務(wù)注冊(cè)。如果我們想要自己取消注冊(cè)(比方說,對(duì)錯(cuò)誤代碼和網(wǎng)絡(luò)接口不可用所作出的響應(yīng))也很容易(用context.unregisterService())。
使用服務(wù)
一旦服務(wù)起來(lái)并運(yùn)行之后,我們就可以用client訪問它了。如果運(yùn)行的是Equinox,你可以用services命令羅列所有已安裝的服務(wù),以及它們是由誰(shuí)注冊(cè)的:
- {com.infoq.shorten.IShorten}={service.id=27}
- Registered by bundle: com.infoq.shorten.trim-1.0.0 [1]
- No bundles using service.
- {com.infoq.shorten.IShorten}={service.id=28}
- Registered by bundle: com.infoq.shorten.tinyurl-1.0.0 [2]
- No bundles using service.
在調(diào)用服務(wù)處理URL之前,client需要解析服務(wù)。我們需要獲得一個(gè)服務(wù)引用,它可以讓我們查看服務(wù)自身內(nèi)部的屬性,然后利用其來(lái)獲得我們感興趣的服務(wù)。可是,我們需要能夠重復(fù)處理相同及不同的URL,以便我們可以把它集成到Equinox或Felix的shell里。實(shí)現(xiàn)如下:
- package com.infoq.shorten.command;
- import org.osgi.framework.BundleContext;
- import org.osgi.framework.ServiceReference;
- import com.infoq.shorten.IShorten;
- public class ShortenCommand {
- protected BundleContext context;
- public ShortenCommand(BundleContext context) {
- this.context = context;
- }
- protected String shorten(String url) throws IllegalArgumentException, IOException {
- ServiceReference ref =
- context.getServiceReference(IShorten.class.getName());
- if(ref == null)
- return null;
- IShorten shorten = (IShorten) context.getService(ref);
- if(shorten == null)
- return null;
- return shorten.shorten(url);
- }
- }
當(dāng)shorten方法被調(diào)用時(shí),上面這段程序?qū)⒉檎曳?wù)引用并獲得服務(wù)對(duì)象。然后我們可以把服務(wù)對(duì)象賦值給一個(gè)IShorten對(duì)象,并使用它與前面講到 的已注冊(cè)服務(wù)進(jìn)行交互。注意這些都是在同一個(gè)VM中發(fā)生的;沒有遠(yuǎn)程調(diào)用,沒有強(qiáng)制異常,沒有參數(shù)被序列化;只是一個(gè)POJO與另一個(gè)POJO對(duì)話。實(shí)際 上,這里與最開始class.forName()例子的唯一區(qū)別是:我們?nèi)绾潍@得shorten POJO。
為了在Equinox和Felix里面使用這一服務(wù),我們需要放一些樣板代碼進(jìn)去。必須提一下,當(dāng)我們定義manifest時(shí),我們可以在Felix和 Equinox命令行上聲明可選依賴,這樣,當(dāng)我們兩者中任何一個(gè)安裝之后,我們就可以運(yùn)行了。(一個(gè)更好的解決方案是將其部署為單獨(dú)的bundles, 這樣我們可以去掉選項(xiàng);但是如果bundle不存在,activator將會(huì)失敗,因此無(wú)法啟動(dòng))。Equinox和Felix特定命令的源代碼在com.infoq.shorten.command bundle中。
如果我們安裝了命令client bundle,我們將得到一個(gè)新命令,shorten,通過OSGi shell可以調(diào)用它。要運(yùn)行該命令,需要先執(zhí)行java -jar equinox.jar -console -noExit或java -jar bin/felix.jar,然后安裝bundle,之后你就可以使用該命令了:
- java -jar org.eclipse.osgi_* -console -noExit
- osgi> install file:///tmp/com.infoq.shorten-1.0.0.jar
- Bundle id is 1
- osgi> install file:///tmp/com.infoq.shorten.command-1.0.0.jar
- Bundle id is 2
- osgi> install file:///tmp/com.infoq.shorten.tinyurl-1.0.0.jar
- Bundle id is 3
- osgi> install file:///tmp/com.infoq.shorten.trim-1.0.0.jar
- Bundle id is 4
- osgi> start 1 2 3 4
- osgi> shorten http://www.infoq.com
- http://tinyurl.com/yr2jrn
- osgi> stop 3
- osgi> shorten http://www.infoq.com
- http://tr.im/Eza8
注意,在運(yùn)行時(shí)TinyURL和Tr.im服務(wù)都是可用的,但是一次只能使用一種服務(wù)??梢栽O(shè)置一個(gè)服務(wù)級(jí)別(service ranking), 這是一個(gè)整數(shù),取值范圍在Integer.MIN_VALUE和Integer.MAX_VALUE之間,當(dāng)服務(wù)最初注冊(cè)時(shí)給 Constants.SERVICE_RANKING賦予相應(yīng)值。值越大表示級(jí)別越高,當(dāng)需要服務(wù)時(shí),會(huì)返回最高級(jí)別的服務(wù)。如果沒有服務(wù)級(jí)別(默認(rèn)值為 0),或者多個(gè)服務(wù)的服務(wù)級(jí)別相同,那么就使用自動(dòng)分配的Constants.SERVICE_PID,可能是任意順序的一個(gè)服務(wù)。
另一個(gè)需注意的問題是:當(dāng)我們停止一個(gè)服務(wù)時(shí),client會(huì)自動(dòng)失敗轉(zhuǎn)移到列表中的下一個(gè)服務(wù)。每當(dāng)該命令執(zhí)行時(shí),它會(huì)獲?。ó?dāng)前)服務(wù)來(lái)處理URL壓 縮需求。如果在運(yùn)行期間服務(wù)提供程序發(fā)生了變化,不會(huì)影響命令的使用,只要有此需求時(shí)有服務(wù)在就成。(如果你停止了所有服務(wù)提供程序,服務(wù)查找將返回 null,這將會(huì)打印出相應(yīng)的錯(cuò)誤信息——好的代碼應(yīng)該確保程序能夠預(yù)防返回服務(wù)引用為null的情況發(fā)生。)
服務(wù)跟蹤
除過每次查詢服務(wù)外,還可以用ServiceTracker來(lái)代替做這一工作。這就跳過了中間獲得ServiceReference的幾步,但是要求你在構(gòu)造之后調(diào)用open,以便開始跟蹤服務(wù)。
對(duì)于ServiceReference,可以調(diào)用getService()獲得服務(wù)實(shí)例。而waitForService()則在服務(wù)不可用時(shí)阻塞一段時(shí)間(根據(jù)指定的timeout。如果timeout為0,則永遠(yuǎn)阻塞)。我們可以如下重新實(shí)現(xiàn)shorten命令:
- package com.infoq.shorten.command;
- import java.io.IOException;
- import org.osgi.framework.BundleContext;
- import org.osgi.util.tracker.ServiceTracker;
- import com.infoq.shorten.IShorten;
- public class ShortenCommand {
- protected ServiceTracker tracker;
- public ShortenCommand(BundleContext context) {
- this.tracker = new ServiceTracker(context,
- IShorten.class.getName(),null);
- this.tracker.open();
- }
- protected String shorten(String url) throws IllegalArgumentException,
- IOException {
- try {
- IShorten shorten = (IShorten)
- tracker.waitForService(1000);
- if (shorten == null)
- return null;
- return shorten.shorten(url);
- } catch (InterruptedException e) {
- return null;
- }
- }
- }
使用Service Tracker的常見問題是在構(gòu)造后忘記了調(diào)用open()。除此之外,還必須在MANIFEST.MF內(nèi)部引入org.osgi.util.tracker包。
使用ServiceTracker來(lái)管理服務(wù)依賴通常被認(rèn)為是管理關(guān)系的好方法。在沒有使用服務(wù)的情況下,查找已輸出的服務(wù)稍微有點(diǎn)復(fù)雜:比 如,ServiceReference在其被解析為一個(gè)服務(wù)之前突然變得不可用了。存在一個(gè)ServiceReference的原因是,相同實(shí)例能夠在多 個(gè)bundle間共享,而且它可以被用來(lái)基于某些標(biāo)準(zhǔn)(手工)過濾服務(wù)。而且,它還可以使用過濾器來(lái)限制可用服務(wù)的集合。
服務(wù)屬性和過濾器
當(dāng)一個(gè)服務(wù)注冊(cè)時(shí),可以將服務(wù)屬性一起注冊(cè)。大多情況下屬性可以為null,但是也可以提供OSGi特定或關(guān)于URL的通用屬性。例如,我們想給服務(wù)分級(jí) 以便區(qū)分優(yōu)先級(jí)。我們可以注冊(cè)Constants.SERVICE_RANKING(代表優(yōu)先級(jí)的數(shù)值),作為最初注冊(cè)過程的一部分。我們可能還想放一些 client想知道的元數(shù)據(jù),比如服務(wù)的主頁(yè)在哪兒,該站點(diǎn)的條款鏈接。為達(dá)此目的,我們需要修改activator:
- public class Activator implements BundleActivator {
- public void start(BundleContext context) {
- Hashtable properties = new Hashtable();
- properties.put(Constants.SERVICE_RANKING, 10);
- properties.put(Constants.SERVICE_VENDOR, "http://tr.im");
- properties.put("home.page", "http://tr.im");
- properties.put("FAQ", "http://tr.im/website/faqs");
- context.registerService(IShorten.class.getName(),
- new Trim(), properties);
- }
- ...
- }
服務(wù)級(jí)別自動(dòng)由ServiceTracker及其他對(duì)象來(lái)管理,但也可以用特定條件來(lái)過濾。Filter是由LDAP風(fēng)格的過濾器改編而來(lái)的,其使用了一種前綴表示法(prefix notation)來(lái) 執(zhí)行多個(gè)過濾。雖然多數(shù)情況下你想提供類的名字(Constants.OBJECTCLASS),但你也可以對(duì)值進(jìn)行檢驗(yàn)(包括限制連續(xù)變量的取值范 圍)。Filter是通過BundleContext創(chuàng)建的;如果你想跟蹤實(shí)現(xiàn)了IShorten接口的服務(wù),并且定義一個(gè)FAQ,我們可以這樣做:
- ...
- public class ShortenCommand
- public ShortenCommand(BundleContext context) {
- Filter filter = context.createFilter("(&" +
- "(objectClass=com.infoq.shorten.IShorten)" +
- "(FAQ=*))");
- this.tracker = new ServiceTracker(context,filter,null);
- this.tracker.open();
- }
- ...
- }
在定義服務(wù)時(shí)可以被過濾或可以設(shè)置的標(biāo)準(zhǔn)屬性包括:
◆service.ranking (Constants.SERVICE_RANKING) - 整數(shù),可以區(qū)分服務(wù)優(yōu)先級(jí)
◆service.id (Constants.SERVICE_ID) - 整數(shù),在服務(wù)被注冊(cè)時(shí)由框架自動(dòng)設(shè)置
◆service.vendor (Constants.SERVICE_VENDOR) - 字符串,表明服務(wù)出自誰(shuí)手
◆service.pid (Constants.SERVICE_PID) - 字符串,代表服務(wù)的PID(persistent identifier)
◆service.description (Constants.SERVICE_DESCRIPTION) - 服務(wù)的描述
◆objectClass (Constants.OBJECTCLASS) - 接口列表,服務(wù)被注冊(cè)在哪些接口下
過濾器語(yǔ)法在OSGi核心規(guī)范的 3.2.7節(jié) “Filter syntax”中有定義。最基本的,它允許如等于(=)、約等于(~=)、大于等于、小于等于以及子字符串比較等操作符。括號(hào)將過流器分組,并且可以結(jié)合 使用“&”、“|” 或“!”分別代表and、or和not。屬性名不是大小寫敏感的,值可能是(如果不用~=作比的話)。“*”是通配符,可用來(lái)支持子字符串匹配,比如 com.infoq.*.*。
總結(jié)
本文中,我們介紹了如何使用服務(wù)進(jìn)行bundle間通信,以替代直接類引用的方法。服務(wù)可以讓模塊系統(tǒng)動(dòng)態(tài)化,這樣就能應(yīng)對(duì)在運(yùn)行時(shí)服務(wù)的變化問題。我們 還接觸到了服務(wù)級(jí)別、屬性及過濾器,并使用標(biāo)準(zhǔn)服務(wù)跟蹤器來(lái)更容易的訪問服務(wù)并跟蹤變化的服務(wù)。