到現在為止,我們已經(jīng)了解了OSGi框架的下面兩層,而作為OSGi框架中最上面的一層,服務(wù)層帶給了我們更多的動(dòng)態(tài)性,并且使用了大家或多或少都曾了解過(guò)的面向服務(wù)編程模型,其好處是顯而易見(jiàn)的。
這里我們依然會(huì )講解什么是服務(wù)層和服務(wù)層對于OSGi框架的意義,此外,還將告訴大家什么時(shí)候應該使用服務(wù),什么時(shí)候不應該使用。最后將會(huì )是OSGi服務(wù)層的一些基礎,包括如何定義、注冊和發(fā)現服務(wù)。
簡(jiǎn)單的說(shuō),服務(wù)就是“為別人所做的工作”,比如兩個(gè)對象互相調用方法,那么被調用者就是在為調用者做工作。 也許有的人會(huì )覺(jué)得服務(wù)是從網(wǎng)絡(luò )上獲取的,但是經(jīng)典的看法其實(shí)并沒(méi)有這個(gè)限制,有時(shí)候即使是完全本地的應用,也能從面向服務(wù)的編程中得到好處。
那么如何將服務(wù)和一次普通的方法調用區別開(kāi)來(lái)呢?其實(shí)一個(gè)服務(wù)可以看作是在服務(wù)的提供者和使用者之間的一個(gè)契約。使用者一般不關(guān)心其實(shí)現的細節,甚至連誰(shuí)提供的都不想知道,只要滿(mǎn)足這個(gè)契約(服務(wù)應該提供什么功能,滿(mǎn)足什么格式)就好了。使用服務(wù)的過(guò)程也包含了發(fā)現服務(wù)和達成協(xié)議的形式,也就是說(shuō)我們需要通過(guò)服務(wù)的標志性特征來(lái)找到對應的服務(wù)。
其實(shí),Java的接口可以說(shuō)提供了一種契約的提供方式,我們能通過(guò)修改classpath來(lái)替換接口的不同的具體實(shí)現。但是OSGi能夠為找到服務(wù)提供更加高層的抽象并且在應用的執行時(shí)動(dòng)態(tài)替換服務(wù)的實(shí)現,這些特性在稍后將會(huì )提到。
服務(wù)(更準確的說(shuō)是面向服務(wù)的編程模型)給予了我們一種即插即用的軟件開(kāi)發(fā)方法,意味著(zhù)更強的靈活性。這種靈活性是如何體現的呢?
支持對多個(gè)競爭實(shí)現(多個(gè)實(shí)現同一個(gè)接口的類(lèi))的篩選:服務(wù)框架會(huì )幫助你記錄服務(wù)的元數據,可以據此幫助使用者查詢(xún)和篩選服務(wù),使用者更加的主動(dòng),這一點(diǎn)和傳統的依賴(lài)注入框架不同。
可以考慮使用的時(shí)候:
當你常常想要對主要的組件進(jìn)行替換和升級而不想重寫(xiě)應用的其他部分,或者當你在程序中想要查找和選擇不同的接口實(shí)現的時(shí)候。
最后,如果不確定是否應該使用服務(wù),可以先用面向接口的方式實(shí)現,這至少是和使用服務(wù)很接近了,并且它也能簡(jiǎn)化你的開(kāi)發(fā)。如果哪天你下定決心想把他們移植到服務(wù)層了,在面向接口的基礎上這個(gè)一直工作也會(huì )變得非常容易。
首先,需要說(shuō)明的是,OSGi的服務(wù)層除開(kāi)前面提到的面向服務(wù)的編程模型,還有一個(gè)區別于其他很多類(lèi)似模型的特性,那就是服務(wù)的完全動(dòng)態(tài)性。也就是說(shuō),當一個(gè)bundle發(fā)現并開(kāi)始使用OSGi中的一個(gè)服務(wù)了以后,這個(gè)服務(wù)可能在任何的時(shí)候改變或者是消失。這方面的內容將在以后更加深入的講解。
OSGi框架有一個(gè)中心化的注冊表,這個(gè)注冊表遵從publish-find-bind模型:
一個(gè)提供服務(wù)的bundle可以發(fā)布POJO作為服務(wù)的實(shí)體;一個(gè)使用服務(wù)的bundle可以通過(guò)這個(gè)注冊表找到和綁定服務(wù)。
我們可以通過(guò)BundleContext接口來(lái)完成上述的工作,下面就是含有這方面功能的接口列表:
public interface BundleContext { ... void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener listener); ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties); ServiceRegistration registerService(String clazz, Object service, Dictionary properties); ServiceRegistration[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException; ServiceRegistration[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException; ServiceReference getServiceReference(String clazz); Object getService(ServiceReference reference); boolean ungetService(ServiceReference reference); ... }為了讓別的bundle能發(fā)現這個(gè)服務(wù),你必須在發(fā)布它之前對其進(jìn)行特征描述。這些特征包括接口的名字(可以是名字的數組),接口的實(shí)現,和一個(gè)可選的java.util.Dictionary類(lèi)型的元數據信息。下面是一個(gè)例子:
String[] interfaces = new String[]{StockListing.class.getName(), StockChart.class.getname()}; Dictionary metadata = new Properties(); metadata.setProperty(“name”, “LSE”); metadata.setProperty(“currency”, Currency.getInstance(“GBP”)); metadata.setProperty(“country”, “GB”); ServiceRegistration registration = bundleContext.registerService(interfaces, new LSE(), metadata); 在上面的代碼中,我們得到了ServiceRegistration對象,我們可以用這個(gè)對象來(lái)更新服務(wù)的元數據:
registration.setProperties(newMetadata);
也可以直接就把這個(gè)服務(wù)移除:
registration.unregister();
需要注意的是這個(gè)對象不能和其他Bundles共享,因為它和發(fā)布服務(wù)的bundle的生命周期相互依存,也就是說(shuō),如果這個(gè)bundle已經(jīng)不在框架執行環(huán)境中存在,那么這個(gè)對象也不應該存在了,“皮之不存毛將焉附”就是這個(gè)道理。
試想如果這個(gè)ServiceRegistration共享給了其他的bundle(具體的說(shuō)就是其他bundle中存在對這個(gè)對象的引用),那么發(fā)布服務(wù)的那個(gè)bundle即使被移除了,由于其他bundle中的引用依然存在,那么垃圾處理機制不會(huì )抹去這個(gè)對象,這樣不但于理不合,而且實(shí)際上這個(gè)對象也是不可用的,因為這個(gè)對象所依存的bundle已經(jīng)不在了。
代碼中的參數new LSE()是一個(gè)POJO,這個(gè)對象不需要實(shí)現任何OSGi類(lèi)型或者使用標注,只要滿(mǎn)足服務(wù)約定(這里就是接口)就可以了。
此外,如果在刪除發(fā)布的服務(wù)之前bundle停止了,框架會(huì )幫助你刪除這些服務(wù)。
上一小節我們說(shuō)明了如何描述和發(fā)布一個(gè)服務(wù),那么現在我們可以根據服務(wù)約定從注冊表中找到正確的服務(wù)。
下面是發(fā)現服務(wù)并獲得其引用的接口:
ServiceReference reference = bundleContext.getServiceReference(StockListing.class.getName()); 這是根據實(shí)現的接口名稱(chēng)獲得的服務(wù),也是最簡(jiǎn)單的方法。
注意這里的reference是服務(wù)對象的間接引用,可是為什么要用間接引用而不直接返回那個(gè)實(shí)際的服務(wù)對象呢?實(shí)際上是為了將服務(wù)的使用和服務(wù)的實(shí)現進(jìn)行解耦,將服務(wù)注冊表作為兩者的中間人,達到跟蹤和控制服務(wù)的目的,同時(shí)還可以在服務(wù)消失了以后通知使用者。
這個(gè)方法的返回類(lèi)型是ServiceReference,它可以在bundle之間互享,因為它和使用服務(wù)的bundle的生命周期無(wú)關(guān)。
在getServiceReference這個(gè)方法中,選擇service的默認優(yōu)先級是先選擇service.rank最高的,在rank相等的情況下選擇最早在框架中注冊的。除了這個(gè)默認的規則,我們還可以在 getServiceReferences中通過(guò)添加過(guò)濾參數(作為調用該方法的第二個(gè)參數)來(lái)做一些篩選。
ServiceReference[] references = bundleContext.getServiceReferences(StockListing.class.getName(), “(&(currency=GBP)(objectClass=org.example.StockChart))”); 在這里的匹配參數是一個(gè)字符串,這個(gè)字符串的格式屬于LDAP查詢(xún)格式,在RFC 1960標準中有完整的描述。
上面的字符串中等號左邊的內容就是前面提到的元數據(Dictionary)中的左值,通過(guò)這個(gè)左值對應的右值來(lái)與服務(wù)所帶有的元數據進(jìn)行匹配。一些簡(jiǎn)單的匹配示例如下:
屬性匹配:
(name=John Smith)
(age>=20)
(age<=65)
模糊匹配:
(name~=johnsmith)
通配符匹配:
(name=Jo*n*Smith*)
判斷某個(gè)屬性是否存在:
(name=)
條件與:
(&(name=John Smith)(occupation=doctor))
條件或:
(|(name~=John Smith)(name~=Smith John))
*條件非: **
(!(name=John Smith))
在你發(fā)現了服務(wù)之后,使用服務(wù)之前,你必須從注冊表中綁定實(shí)現的服務(wù)。
StockListing listing = (StockListing) bundleContext.getService(reference);這個(gè)方法返回的POJO實(shí)例和之前在注冊表中注冊的實(shí)例是同一個(gè)。
每次使用getService方法的時(shí)候,注冊表會(huì )將對應服務(wù)的使用次數加1,同時(shí)會(huì )記錄誰(shuí)在使用這個(gè)服務(wù)。所以當你不在想使用這服務(wù)的時(shí)候,最好告訴注冊表一聲。
bundleContext.ungetService(reference); listing = null; 給出第二條語(yǔ)句的目的并不是為了通知注冊表,而是為了讓java的垃圾處理機制安全運作。因為這里我們用了一個(gè)局部變量listing來(lái)作為服務(wù)對象的一個(gè)引用,(不妨假設listing是最后一個(gè)引用這個(gè)對象的變量),如果我們不設為null,那么在這個(gè)listing消亡之前,那個(gè)服務(wù)對象有可能不會(huì )被垃圾處理掉(即使在程序邏輯上這個(gè)服務(wù)對象已經(jīng)是“垃圾”了),這可能會(huì )引發(fā)一些問(wèn)題。
不過(guò),這種用局部變量引用服務(wù)對象的方式本來(lái)就不對。一般來(lái)說(shuō),還是應該在每次需要使用的時(shí)候臨時(shí)從ServiceReference獲得,并且要考慮到這個(gè)服務(wù)在任何時(shí)候都有可能消亡。
至此,三個(gè)層次的基礎性講解已經(jīng)全部結束了。三篇入門(mén)級文檔以后,相信讀者對OSGi框架已經(jīng)有了一個(gè)大致的了解,接下來(lái)建議大家配合《OSGi開(kāi)發(fā)環(huán)境的建立和HelloWorld》這篇文檔自己實(shí)際操作一下之前講到的一些內容,鞏固一下這個(gè)系列的知識,為接下來(lái)的進(jìn)階篇內容做好準備。
聯(lián)系客服