Reference:http://www.knopflerfish.org/osgi_service_tutorial.html OSGi是Java里面的一个模块框架标准,Eclipse就是基于OSGi所开发,开源的OSGi实现大概包括Eclipse的Equinox,Apache的Felix,还有Knopflerfish,这些都是实现了OSGi R4标准的,其它的还有R3标准的,就不提了。 要做OSGI开发的话感觉还是Knopflerfish好点,Equinox本身位于Eclipse之中,如果不用Eclipse的话会比较恶心,Apache的那个例子太少,而且控制台界面也比较难用,我本来前段时间打算学Apache的Felix的,结果官方上面的文档太不全了,实在看不下去。Knopflerfish有一个Swing写的管理程序,文档虽然还是比较少,但起码比Felix要顺眼多了。 Knopflerfish的文档里面着重对Service这块进行了描述,我之前看Felix的时候都没看到过这个概念,当时就纳闷bundle直接怎么样去交互。搞了半天原来还有个Service的概念。。。 下面把官方的文档自己翻译一下,权当练习英文:)
Contents
- 什么是Service?
- Service Factory又是干啥的?
- Service都用来做什么用?
- Service怎么被别人访问?
- 释放Service?
- Service的最佳实践
- 白板模式
什么是Service
这里推荐大家阅读OSGiR3 specification, chapter 4.10以及BundleContext的API文档以获得更详细的了解.
OSGiService的使用者毫无例外应该是一个OSGI的Bundle,也就是实现BundleActivator的类。
每个Bundle可以注册0个或多个Service,同样,它也可以使用0个或者多个Service。对Service的起限制作用的是内存的大小或者Java的安全策略。
无论是发布,注册,使用Service都可以通过Java的安全机制进行权限控制。
>以下代码展示了如何注册一个简单的Service。
Long i = new Long(20); Hashtable props = new Hashtable(); props.put("description", "This an long value"); bc.registerService(Long.class.getName(), i, props);
注意:一个Service可以被注册成多个接口,这种情况下,注册为Service的对象就必须要实现所有的接口方法。
什么是Service Factory
这样做的好处在于有时候不同的Bundle需要不同的Service配置,举例来说,日志Service需要能够打印出记录日志的是哪个Bundle,不然日志也会变得混乱而难以阅读。
Service Factory也需要注册,就像注册一个Service一样调用registerService方法,但有点不同的是ServiceFactory 是间接使用。(需要隐式调用factory.getService(..)?)
当Bundle使用Service的时候它不需要也不应该去关心到底这个Service是从ServiceFactory间接得来还是直接就是一个对象。
class LongFactory implements ServiceFactory { public Object getService(Bundle bundle, ServiceRegistration reg) { // each bundle gets a Long with it's own id return new Long(bundle.getBundleId()); } void ungetService(Bundle bundle, ServiceRegistration reg, Object service) { // nothing needed in this case } } ServiceFactory factory = new LongFactory(); bc.registerService(Long.class.getName(), factory, new Hashtable());
注意:框架会对ServiceFactory生成的Service对象做缓存出来,这样就是说,对于每一个Bundle只会生成一个对应的Service对象。
Service都用来干啥?
Service是一个比较笼统的概念,不过它主要还是被设计为用在以下的场景中:
- 将功能子模块从一个模块导出到另一个模块,实现子模块重用。
- 从别个模块导入一些功能函数子模块
- 通过Service在别的模块里面注册监听器
- 把诸如UPnp设备或者其它硬件设备暴露给其它的OSGIBundle,See the Device and UPnP APIs
- 通过UPnP或者SOAP协议把java和其它平台语言结合起来。
- 通过 Configuration Manager实现对Bundle的配置管理
总的来说,Service是Bundle之间用来通信的首选方法
Service怎样被访问到
- 得到一个 ServiceReference
- 用 BundleContext.getService()从Service Reference里面得到相应的Service对象。
- 直接调用BundleContext.getService() 这样会返回一个对象或者返回Null。
- 直接调用BundleContext.getServices() 返回完全匹配的对象。(1,2有点晕了)
- 在 ServiceListener 的方法回调中得到
- 使用工具类 ServiceTracker
上述四种方式除了第一种,其它的都可以通过指定一个LDAP filters 来对Service进行精确定位,这样经过过滤之后就可能有0个或多个匹配的Service对象。
典型的Filter包括一个类名和一组属性值。下面的例子匹配一个带有Port属性为80的HTTP Service。
(&(objectclass=org.osgi.service.http.HttpService)(port=80))
一旦ServiceReference就绪之后,Service就可以通过强制转型得到
HttpService http = (HttpService)bc.getService(sr);
这样,Service对象可以像普通对象一样被访问,实际上,Service对象也完全是一个普通的Java对象,更具体的说,这个对象就是Bundle在注册Service的时候所创建的那个对象。
释放Service
一旦Service对象不再使用,使用者需要调用 BundleContext.ungetService() 方法来释放资源.
bc.ungetService(sr);
另一方面,当使用者所在的Bundle停止的时候所有的相关联的Service都会自动被释放,所以,在BundleStop方法这里无需再添加Service对象的清理过程。
注意:Service对象只有在它真正不被使用的时候才可以释放。有些Service可能会保持一些通用的状态信息,释放Service对象的同时这些状态信息也可能同时被释放,这个要视乎Service本身的行为。前面提到的HttpService就会移除所有的Servlet和资源一旦有一个使用者释放了这个HTTP Service。
当Service释放的时候,相应的ServiceListener会收到相应的ServiceEvent.UNREGISTERING事件通知。
当Service注销掉以后,再调用GetService反复只会得到一个Null对象。
注意:很常见的一种情况就是使用者通过变量保存了一个Service对象,在Service注销过后,这个变量所指向的Service将不能继续使用,这个时候应该将这个变量置为Null以便GC能够回收内存。
最佳实践
最佳实践:
- 总是记住Service在调用的时候就像RMI,总是有可能会出现异常,所以程序在调用的时候应该记得在处理异常,也并不是说每次调用都需要TryCatch,可以在一个比较高的层次统一处理这些异常。
- 使用白板模式
- 尽可能的把代码构造成一个Service给别的Bundle使用而不是从别的Bundle获取Service
- 使用侦听器 ServiceListener 和 ServiceTracker p提供了不同Level的获取Service的方法。
- 使用声明式的ServiceBinder ServiceBinder 通过XML对Service的依赖关系进行描述。
ServiceReference sr = bc.getServiceReference(HttpService.class.getName()); HttpService http = (HttpService)bc.getService(sr); http.registerServlet(....)这里每一行都有可能会Fail
- 如果HTTpService没有注册,ServiceReference 可能会为Null,这样第二行就会有NullPointerException。
- HTTP Service可能会因为没有授权或者说刚好HTTP Service在第一行代码跟第二行代码直接被注销,这样都会造成第三行的地方出现NullPointerException。
- HTTPService随时可能会变得不可用,所以第三行也极有可能出现IllegalStateException
上述的异常情形通过条件判断可用简单的避免:
ServiceReference sr = bc.getServiceReference(HttpService.class.getName()); if(sr != null) { HttpService http = (HttpService)bc.getService(sr); if(http != null) { http.registerServlet(....) } }
不过这种方式很快就显得很笨重,而且多个IF,ELSE也会让代码变得难以捉摸。
使用ServiceListener是个不错的解决方案:
ServiceListener sl = new ServiceListener() { public void ServiceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); switch(ev.getType()) { case ServiceEvent.REGISTERED: { HttpService http = (HttpService)bc.getService(sr); http.registerServlet(...); } break; default: break; } } }; String filter = "(objectclass=" + HttpService.class.getName() + ")"; try { bc.addServiceListener(sl, filter); } catch (InvalidSyntaxException e) { e.printStackTrace(); }
上述代码中唯一可能出现异常的在第8行,不过这个异常通过是又框架在事件分发Code中处理的,所以如果没有特殊的要求,上述代码就已经足够了。
另外还有一个问题,如果所有的HTTPService都已经被注册了,那么上述的Listener可能会因为HttpService重启而Fail。手动去构造ServiceEvent然后调用Listener可以解决这个问题:
try { bc.addServiceListener(sl, filter); ServiceReference[] srl = bc.getServiceReferences(null, filter); for(int i = 0; srl != null && i InvalidSyntaxException e) { e.printStackTrace(); }
使用ServiceListener的话,上述的代码已经达到了25行之多,使用ServiceTracker会更加简洁:
ServiceTracker logTracker = new ServiceTracker(bc,LogService.class.getName(), null); logTracker.open(); ((LogService)logTracker.getService()).doLog(...);
ServiceTracker负责保证所有的Service都是可用的,如果某个Service不再可用则返回Null。这样的话就只需要再关系RuntimeException了。
下面的代码对ServiceTracker进行了封装,这样调用的时候就更加简单了:
ServiceTracker logTracker; void init() { logTracker = new ServiceTracker(bc, LogService.class.getName(), null); } LogService getLog() { return (LogService)logTracker.getService(); } void test() { getLog().doLog(...); }
白板模式
考虑以下HTTPService 这个例子。调用者通过需要把它们的Servlet加到WebServer当中,这就需要一堆的查找,使用Service的代码。
另外,HTTPService必须提供额外的方法供增加和移除Servlets的时候调用,而且必须维护一个Servlet的列表。这样会增加API和代码本身的复杂性。
把Servlets注册为Service,让HTTPServer来使用这些Service不失为一种更好的方法。
class MyServlet extends HttpServlet { ... } HttpServlet servlet = new MyServlet(); Hashtable props = new Hashtable(); props.put("alias", "/servlets/foo"); // desired http alias ServiceRegistration reg = bc.registerService(HttpServlet.class.getName(), servlet, props);
客户端只需要把HTTPServlet注册为Service即可。
reg.unregister();下面的代码展示了对HTTP Service的包装,完整的代码见Http Console例子
void open() { httpTracker = new ServiceTracker(bc, HttpService.class.getName(), null); httpTracker.open(); ServiceListener sl = new ServiceListener() { public void serviceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); switch(ev.getType()) { case ServiceEvent.REGISTERED: { registerServlet(sr); } break; case ServiceEvent.UNREGISTERING: { unregisterServlet(sr); } break; } } }; String filter = "(objectclass=" + HttpServlet.class.getName() + ")"; try { bc.addServiceListener(sl, filter); ServiceReference[] srl = bc.getServiceReferences(null, filter); for(int i = 0; srl != null && i < servlet =" (HttpServlet)bc.getService(sr);" alias =" (String)sr.getProperty(" httplist =" httpTracker.getServices();" i =" 0;" http =" (HttpService)httplist[i];" props =" new" alias =" (String)sr.getProperty(" httplist =" httpTracker.getServices();" i =" 0;" http =" (HttpService)httplist[i];">
0 评论:
发表评论