OSGi-入门篇之服务层(03)
前言
作為OSGi框架中最上面的一層,服務層帶給了我們更多的動態性,并且使用了大家或多或少都曾了解過的面向服務編程模型,其好處是顯而易見的。
1 什么是服務
簡單的說,服務就是“為別人所做的工作”,比如兩個對象互相調用方法,那么被調用者就是在為調用者做工作。
那么如何將服務和一次普通的方法調用區別開來呢?其實一個服務可以看作是在服務的提供者和使用者之間的一個契約。使用者一般不關心其實現的細節,甚至連誰提供的都不想知道,只要滿足這個契約(服務應該提供什么功能,滿足什么格式)就好了。使用服務的過程也包含了發現服務和達成協議的形式,也就是說我們需要通過服務的標志性特征來找到對應的服務。
其實,Java的接口可以說提供了一種契約的提供方式,我們能通過修改classpath來替換接口的不同的具體實現。但是OSGi能夠為找到服務提供更加高層的抽象并且在應用的執行時動態替換服務的實現,這些特性在稍后將會提到。
2 為什么要使用服務
服務(更準確的說是面向服務的編程模型)給予了我們一種即插即用的軟件開發方法,意味著更強的靈活性。這種靈活性是如何體現的呢?
- 低耦合,利于組件復用:通過服務我們能夠清晰的定義組件的邊界,從而將服務的使用者和提供者之間的耦合度降到很低。
- 更加強調接口而不是在具體的實現:Java的interface提供了一種形式的契約,在OSGi的服務層中充分利用了接口特性的優勢,這樣使得無論有多少個類實現了這個接口,只要滿足對這個接口的功能需要,就可以被使用者使用。
- 對于依賴有比較清晰地描述:單是接口本身只包含服務的名稱和參數類型,并不足以清楚的描述服務的所有特征,而面向服務的編程模型中要求了更加清晰的描述使得這些特征能夠唯一標識一個服務。
-
支持對多個競爭實現(多個實現同一個接口的類)的篩選:服務框架會幫助你記錄服務的元數據,可以據此幫助使用者查詢和篩選服務,使用者更加的主動,這一點和傳統的依賴注入框架不同。
3 什么時候使用/不使用服務
-
可以考慮使用的時候:?
當你常常想要對主要的組件進行替換和升級而不想重寫應用的其他部分,或者當你在程序中想要查找和選擇不同的接口實現的時候。 - 不應該使用的時候:?
服務的加入和維護往往持續的增加框架的開銷,所以當你開發的代碼對性能需求敏感的時候,不要使用服務。?
顯然它也不應該出現在兩段經常一起開發和更新的緊耦合代碼之間,除非你真的需要在自己寫的代碼中得到“一個接口多個實現”的選擇權。
最后,如果不確定是否應該使用服務,可以先用面向接口的方式實現,這至少是和使用服務很接近了,并且它也能簡化你的開發。如果哪天你下定決心想把他們移植到服務層了,在面向接口的基礎上這個一直工作也會變得非常容易。
4 OSGi服務層基礎
首先,需要說明的是,OSGi的服務層除開前面提到的面向服務的編程模型,還有一個區別于其他很多類似模型的特性,那就是服務的完全動態性。也就是說,當一個bundle發現并開始使用OSGi中的一個服務了以后,這個服務可能在任何的時候改變或者是消失。這方面的內容將在以后更加深入的講解。?
OSGi框架有一個中心化的注冊表,這個注冊表遵從publish-find-bind模型:
一個提供服務的bundle可以發布POJO作為服務的實體;一個使用服務的bundle可以通過這個注冊表找到和綁定服務。?
我們可以通過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)throwsInvalidSyntaxException;
ServiceReference getServiceReference(String clazz);
Object getService(ServiceReference reference);
boolean ungetService(ServiceReference reference);
...}
4.1 發布服務
為了讓別的bundle能發現這個服務,你必須在發布它之前對其進行特征描述。這些特征包括接口的名字(可以是名字的數組),接口的實現,和一個可選的java.util.Dictionary類型的元數據信息。下面是一個例子:
String[] interfaces =newString[]{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對象,我們可以用這個對象來更新服務的元數據:?
registration.setProperties(newMetadata);
也可以直接就把這個服務移除:?
registration.unregister();
需要注意的是這個對象不能和其他Bundles共享,因為它和發布服務的bundle的生命周期相互依存,也就是說,如果這個bundle已經不在框架執行環境中存在,那么這個對象也不應該存在了,“皮之不存毛將焉附”就是這個道理。
試想如果這個ServiceRegistration共享給了其他的bundle(具體的說就是其他bundle中存在對這個對象的引用),那么發布服務的那個bundle即使被移除了,由于其他bundle中的引用依然存在,那么垃圾處理機制不會抹去這個對象,這樣不但于理不合,而且實際上這個對象也是不可用的,因為這個對象所依存的bundle已經不在了。
代碼中的參數new LSE()是一個POJO,這個對象不需要實現任何OSGi類型或者使用標注,只要滿足服務約定(這里就是接口)就可以了。
此外,如果在刪除發布的服務之前bundle停止了,框架會幫助你刪除這些服務。
4.2 發現和綁定服務
上一小節我們說明了如何描述和發布一個服務,那么現在我們可以根據服務約定從注冊表中找到正確的服務。?
下面是發現服務并獲得其引用的接口:
這是根據實現的接口名稱獲得的服務,也是最簡單的方法。
注意這里的reference是服務對象的間接引用,可是為什么要用間接引用而不直接返回那個實際的服務對象呢?實際上是為了將服務的使用和服務的實現進行解耦,將服務注冊表作為兩者的中間人,達到跟蹤和控制服務的目的,同時還可以在服務消失了以后通知使用者。
這個方法的返回類型是ServiceReference,它可以在bundle之間互享,因為它和使用服務的bundle的生命周期無關。
4.2.1 選擇最適合你的服務
在getServiceReference這個方法中,選擇service的默認優先級是先選擇service.rank最高的,在rank相等的情況下選擇最早在框架中注冊的。除了這個默認的規則,我們還可以在 getServiceReferences中通過添加過濾參數(作為調用該方法的第二個參數)來做一些篩選。
ServiceReference[] references = bundleContext.getServiceReferences(StockListing.class.getName(),“(&(currency=GBP)(objectClass=org.example.StockChart))”);在這里的匹配參數是一個字符串,這個字符串的格式屬于LDAP查詢格式,在RFC 1960標準中有完整的描述。
上面的字符串中等號左邊的內容就是前面提到的元數據(Dictionary)中的左值,通過這個左值對應的右值來與服務所帶有的元數據進行匹配。一些簡單的匹配示例如下:?
屬性匹配:?
(name=John Smith)?
(age>=20)?
(age<=65)?
模糊匹配:?
(name~=johnsmith)?
通配符匹配:?
(name=Jo*n*Smith*)?
判斷某個屬性是否存在:?
(name=)?
條件與:?
(&(name=John Smith)(occupation=doctor))?
條件或:?
(|(name~=John Smith)(name~=Smith John))?
*條件非: **?
(!(name=John Smith))
4.2.2 綁定和使用服務
在你發現了服務之后,使用服務之前,你必須從注冊表中綁定實現的服務。
StockListing listing =(StockListing) bundleContext.getService(reference);這個方法返回的POJO實例和之前在注冊表中注冊的實例是同一個。
每次使用getService方法的時候,注冊表會將對應服務的使用次數加1,同時會記錄誰在使用這個服務。所以當你不在想使用這服務的時候,最好告訴注冊表一聲。
bundleContext.ungetService(reference); listing =null;給出第二條語句的目的并不是為了通知注冊表,而是為了讓java的垃圾處理機制安全運作。因為這里我們用了一個局部變量listing來作為服務對象的一個引用,(不妨假設listing是最后一個引用這個對象的變量),如果我們不設為null,那么在這個listing消亡之前,那個服務對象有可能不會被垃圾處理掉(即使在程序邏輯上這個服務對象已經是“垃圾”了),這可能會引發一些問題。
不過,這種用局部變量引用服務對象的方式本來就不對。一般來說,還是應該在每次需要使用的時候臨時從ServiceReference獲得,并且要考慮到這個服務在任何時候都有可能消亡。
總結
以上是生活随笔為你收集整理的OSGi-入门篇之服务层(03)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IOT/智能设备日志解决方案(1):概述
- 下一篇: java实现-两个栈实现一个队列和两个队