在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,大家可以根据自己的需要,选择最合适的使用方式。