OSGi中獲取Service的幾種方式

OSGi中獲取Service的幾種方式

在OSGi中,Service是動態管理的,OSGi容器提供的好幾種獲取和使用Service的方式,那麼這幾種方式各有什麼優、缺點呢,下面我們就以org.osgi.service.log.LogService爲例來分別講一講。


一。最原始的方式:
 1         // 獲取Service引用
 2         ServiceReference ref = context.getServiceReference(LogService.class.getName());
 3         if (ref != null) {
 4             // 獲取Service實例
 5             LogService service = (LogService) context.getService(ref);
 6             if (service != null) {
 7                 // 調用Service方法
 8                 service.log(LogService.LOG_INFO, "ok");
 9                 // 釋放Service,在此之後不應該再繼續使用Service實例
10                 context.ungetService(ref);
11             }
12         }
優點:很難說有什麼優點,硬要說幾句的話,那就是邏輯夠簡單,調用最少,適合一次性操作。
缺點:需要判斷返回值是否爲null,需要手動申請和釋放service,由於OSGi的動態性,請在獲取ref後儘快使用,無法保證ref長期有效。每次訪問都會有service獲取和釋放的開銷。
用途:適合於不頻繁的調用service,且在service不可用時也能繼續執行後續操作的場景。

二。使用ServiceListener:
在Service註冊時訪問:
 1         context.addServiceListener(new ServiceListener() {
 2             public void serviceChanged(ServiceEvent event) {
 3                 switch (event.getType()) {
 4                 case ServiceEvent.REGISTERED:
 5                     // 獲取Service引用
 6                     ServiceReference ref = event.getServiceReference();
 7                     // 獲取Service實例
 8                     LogService service = (LogService) context.getService(ref);
 9                     if (service != null) {
10                         // 調用Service方法
11                         service.log(LogService.LOG_INFO, "ok");
12                         // 釋放Service,在此之後不應該再繼續使用Service實例
13                         context.ungetService(ref);
14                     }
15                     break;
16                 case ServiceEvent.UNREGISTERING:
17 
18                     break;
19                 }
20 
21             }
22         }, "(objectclass=org.osgi.service.log.LogService)");
獨立於ServiceListener的訪問:類似於方式一,在Listener中獲取service並且保存到成員變量中,以供後續訪問:
 1         context.addServiceListener(new ServiceListener() {
 2             public void serviceChanged(ServiceEvent event) {
 3                 switch (event.getType()) {
 4                 case ServiceEvent.REGISTERED:
 5                     if (ref == null) {
 6                         ref = event.getServiceReference();
 7                         service = (LogService) context.getService(ref);//保存實例以備後續訪問
 8                     }
 9                     break;
10                 case ServiceEvent.UNREGISTERING:
11                     if (ref == event.getServiceReference()) {
12                         context.ungetService(ref);//釋放實例
13                         service = null;
14                         ref = null;
15                     }
16                     break;
17                 }
18 
19             }
20         }, "(objectclass=org.osgi.service.log.LogService)");
訪問Service:
1         if (service != null) service.log(LogService.LOG_INFO, "ok");
優點:只在Service變更時產生一次service獲取開銷,動態感知service的註冊和註銷。
缺點:在ServiceListener註冊之前已經存在的Service無法監聽到。需要自己維護service的獲取和釋放。在需要監聽多個Service實例時,使用並不方便。

三、使用ServiceTracker
ServiceTracker其實是對ServiceListener實現方式的封裝,使得對service的獲取更加簡潔,同時也解決了不能監聽到已經存在的Service的問題(其實就是在增加ServiceListener的同時調用BundleContext.getAllServiceReferences方法以獲取現有的Service引用)。
使用ServiceTracker使得獲取Service的代碼更加簡潔和一致,不必再考慮Service是否存在的問題,並且ServiceTracker也提供了更加有效的監聽Service的方式。
一次性訪問:
1         ServiceTracker tracker = new ServiceTracker(context, LogService.class.getName(), null);
2         tracker.open();
3         LogService service = (LogService) tracker.getService();
4         if (service != null) service.log(LogService.LOG_INFO, "ok");
5         // 獲取多個Service
6         Object[] services = tracker.getServices();
7         // 獲取Service的數量
8         int count = tracker.getTrackingCount();
9         tracker.close();
在Service註冊和註銷時訪問:
 1         ServiceTracker tracker = new ServiceTracker(context, LogService.class.getName(), null) {
 2             @Override
 3             public Object addingService(ServiceReference reference) {
 4                 LogService service = (LogService) super.addingService(reference);
 5                 if (service != null) service.log(LogService.LOG_INFO, "ok");
 6                 return service;
 7             }
 8 
 9             @Override
10             public void removedService(ServiceReference reference, Object service) {
11                 ((LogService) service).log(LogService.LOG_INFO, "removedService");
12                 super.removedService(reference, service);
13             }
14         };
15         tracker.open();
16 
17         // 在自身lifecycle結束時關閉tracker
18         tracker.close();
有一點需要注意的是,tracker需要調用open方法才能監聽到Service,另外,在bundle stop以後,bundle內open的ServiceTracker不會自動關閉,所以一定不要忘記在bundle結束之前,關閉所有在bundle中open的ServiceTracker。

四、使用Declarative Services
在OSGi 4以後的規範中,增加了Declarative Services方式。Declarative Services 是一個面向服務的組件模型,它制訂的目的是更方便地在 OSGi 服務平臺上發佈、查找、綁定服務,對服務進行動態管理,如監控服務狀態以及解決服務之間的複雜的依賴關係等問題。Declarative Services 採用服務組件的延遲加載以及組件生命週期管理的方式來控制對於內存的佔用以及啓動的快速,很好的解決了傳統的 OSGi 服務模型在開發和部署比較複雜應用時內存佔用大、啓動慢等問題,並且對服務組件的描述採用XML來實現,十分便於用戶理解和使用。
在equinox-SDK-3.6M5開發包中,包含了一個DS的實現:org.eclipse.equinox.ds_1.2.0.v20100125.jar,將這個jar和一個依賴的jar:org.eclipse.equinox.util_1.0.100.v20090520-1800.jar部署到OSGi容器中,就可以使用DS服務了。equinox中DS服務的實現,是綜合使用了BundleListener,ServiceListener等相關OSGi API,將大量繁雜和冗長的代碼細節藏在了實現背後,開發者只需要瞭解簡單的xml語法和配置方式即可方便的使用。
要使用DS,一般有以下幾個步驟:
1.定義Component實現類:
 1 package org.dbstar.osgi.dstest;
 2 
 3 import org.osgi.service.component.ComponentContext;
 4 import org.osgi.service.log.LogService;
 5 
 6 public class TestComponent {
 7     public void activate(ComponentContext context) {
 8         System.out.println("activate(" + context + ")");
 9     }
10 
11     public void deactivate(ComponentContext context) {
12         System.out.println("deactivate(" + context + ")");
13     }
14 
15     public void modified(ComponentContext context) {
16         System.out.println("modified(" + context + ")");
17     }
18 
19     public void bind(LogService service) {
20         service.log(LogService.LOG_INFO, "bind");
21     }
22 
23     public void unbind(LogService service) {
24         service.log(LogService.LOG_INFO, "unbind");
25     }
26 }
2.編寫component.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
3     activate="activate" deactivate="deactivate" modified="modified" name="test"
4     xsi:schemaLocation="http://www.osgi.org/xmlns/scr/v1.1.0 http://www.osgi.org/xmlns/scr/v1.1.0">
5     <implementation class="org.dbstar.osgi.dstest.TestComponent" />
6     <reference bind="bind" cardinality="1..1"
7         interface="org.osgi.service.log.LogService" name="LogService"
8         policy="dynamic" unbind="unbind" />
9 </scr:component>
以上是有namespace的xml寫法,在equinox中也支持沒有namespace的寫法,Eclipse中有相應的插件來提供圖形化的界面來維護component xml。以下是沒有namespace的xml寫法:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <component name="test">
5     <implementation class="org.dbstar.osgi.dstest.TestComponent" />
6     <reference bind="bind" cardinality="1..1"
7         interface="org.osgi.service.log.LogService" name="LogService"
8         policy="dynamic" unbind="unbind" />
9 </component>
3.將寫好的xml放置到bundle根目錄下的OSGI-INF下面
4.在bundle的描述文件META-INF/MANIFEST.MF中增加component相關的header:
1 Service-Component: OSGI-INF/component.xml
注意xml的文件名不是絕對的,放置的目錄也不是絕對的,只要在Service-Component中包含正確的路徑就可以了。
一個bundle可以註冊多個component,只要編寫多個xml文件即可,在Service-Component中以逗號分隔。
Component的註冊並不依賴Activator,所以bundle的Activator不是必須的。
另外在我的使用過程中,發現一個問題,如果xml中沒有使用namespace,那麼component節點上的幾個callback類屬性都不能定義,例如activate屬性。如果使用了namespace,那麼這些屬性都是可以正常使用的,不知道這算不算是bug。
關於DS規範的詳細內容,可以參見:OSGi 中的 Declarative Services 規範簡介

最後總結一下,綜上所述的四種獲取service的方法,使得service的獲取越來越簡單,開發者只需關注自己的邏輯,而不必糾纏於OSGi繁瑣的Service Lookup中去,同時還提供了更加方便使用的API,大家可以根據自己的需要,選擇最合適的使用方式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章