OSGi学习之Service Tutorial

 

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

  1. 什么是Service?
  2. Service Factory又是干啥的?
  3. Service都用来做什么用?
  4. Service怎么被别人访问?
  5. 释放Service?
  6. Service的最佳实践
  7. 白板模式

什么是Service

一个OSGi Service简单的说就是一个带有一堆属性(Properties)的Java 对象,跟普通对象的区别就在于它在OSGi框架里面进行了注册。任何的Java对象都开源注册成一个Service,但通常Service都是一些实现了某些接口可以供别的Bundle通过接口定义进行调用的对象。

这里推荐大家阅读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

Service Factory是一个特殊的类,它为每一个前来使用Service的bundle提供一个独立的进程。(大概就是每个Bundle都是用的Service的不同对象,互相之间不会有影响)

这样做的好处在于有时候不同的Bundle需要不同的Service配置,举例来说,日志Service需要能够打印出记录日志的是哪个Bundle,不然日志也会变得混乱而难以阅读。

Service Factory也需要注册,就像注册一个Service一样调用registerService方法,但有点不同的是ServiceFactory 是间接使用。(需要隐式调用factory.getService(..)?)

当Bundle使用Service的时候它不需要也不应该去关心到底这个Service是从ServiceFactory间接得来还是直接就是一个对象。

一个简单的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怎样被访问到

Service通常是通过Service References来引用一个唯一的Service对象。
为了访问Service,以下两个步骤是必须的:
  1. 得到一个 ServiceReference
  2. BundleContext.getService()从Service Reference里面得到相应的Service对象。
而ServiceReference则是通过下列步骤得到:
  1. 直接调用BundleContext.getService() 这样会返回一个对象或者返回Null。
  2. 直接调用BundleContext.getServices() 返回完全匹配的对象。(1,2有点晕了)
  3. ServiceListener 的方法回调中得到
  4. 使用工具类 ServiceTracker

上述四种方式除了第一种,其它的都可以通过指定一个LDAP filters 来对Service进行精确定位,这样经过过滤之后就可能有0个或多个匹配的Service对象。

典型的Filter包括一个类名和一组属性值。下面的例子匹配一个带有Port属性为80的HTTP Service。

LDAP Filter字符串
(&(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的状态极不稳定,所以任何时候都有可能会变得不可用。

最佳实践:

  • 总是记住Service在调用的时候就像RMI,总是有可能会出现异常,所以程序在调用的时候应该记得在处理异常,也并不是说每次调用都需要TryCatch,可以在一个比较高的层次统一处理这些异常。
  • 使用白板模式
  • 尽可能的把代码构造成一个Service给别的Bundle使用而不是从别的Bundle获取Service
  • 使用侦听器 ServiceListenerServiceTracker p提供了不同Level的获取Service的方法。
  • 使用声明式的ServiceBinder ServiceBinder 通过XML对Service的依赖关系进行描述。
考虑一下下面这种普遍的不好的写法:
ServiceReference sr  =  bc.getServiceReference(HttpService.class.getName());
HttpService http     = (HttpService)bc.getService(sr);
http.registerServlet(....)
这里每一行都有可能会Fail
  1. 如果HTTpService没有注册,ServiceReference 可能会为Null,这样第二行就会有NullPointerException。
  2. HTTP Service可能会因为没有授权或者说刚好HTTP Service在第一行代码跟第二行代码直接被注销,这样都会造成第三行的地方出现NullPointerException。
  3. 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可以解决这个问题:

Construct ServiceEvents - contd. from above
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 example
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(...);
}

白板模式

"Don't call getService(), call registerService() instead!" 不要试图调用getService()而是要用RegisterService()来代替。

考虑以下HTTPService 这个例子。调用者通过需要把它们的Servlet加到WebServer当中,这就需要一堆的查找,使用Service的代码。

另外,HTTPService必须提供额外的方法供增加和移除Servlets的时候调用,而且必须维护一个Servlet的列表。这样会增加API和代码本身的复杂性。

把Servlets注册为Service,让HTTPServer来使用这些Service不失为一种更好的方法。

Servlet client
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例子
HttpService wrapper
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 评论: