先看一段典型的获取并使用服务的代码:
void logMessage(String msg) {
LogService logService = null;
BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceReference ref = context.getServiceReference(LogService.class);
if (ref != null) {
logService = context.getService(ref);
}
if (logService != null) {
try {
logService.log(LogService.LOG_INFO, msg);
}
finally {
context.ungetService(ref);
}
}
else {
System.out.println(msg);
}
}
上面代码的作用是使用标准日志服务记录一行日志。估计你看完就晕了,我也是。。怪不得OSGi是如此的难用。。
好了,书归正传,我们看一下这段代码里的几个问题:
1、为什么要分两阶段来获取服务?
2、为什么需要ungetService?
先看第1个问题:为什么要分两阶段来获取服务?
ServiceReference ref = context.getServiceReference(LogService.class);
if (ref != null) {
logService = context.getService(ref);
}
我的理解:这是OSGi框架中 服务动态性和性能 折衷的结果。
首先,OSGi以服务动态性为设计目标,即:允许在不停机(这儿是指JVM)的状况下,动态的更新升级服务。
要实现这一点,首先,服务客户端(比如上面的logMessage方法)应该在每次使用服务时,动态的去查询获取最新的服务对象,
而不应该持久的存储服务对象的引用----就像上面方法展示的那样。
我们知道,OSGi模块层一个核心的特性就是支持同一个类库(这儿就是Bundle)的不同版本并存,并且按需连接到需要的客户端;
从而解决所谓的DLL地狱问题。
这意味着相同包名、相同类名、但却完全不同的Class可以并存在JVM中。服务(Service)同样存在这样的问题,
即:不同Bundle可以注册具有相同服务名称(接口或者类的完全限定名)的服务。
所以,在从服务注册表中查询服务时,一件很重要的事情就是进行“”可见性过滤“”。
public boolean isAssignableTo(Bundle bundle, String className);
ServiceReference.isAssignableTo方法就用来做这种可见性检查。
由于OSGi复杂的类加载机制,这个检查是相对耗时的操作,不适合频繁操作。
所以,一般的用法是缓存ServiceReference对象,而每次在使用前再调用context.getService(ref)来获取动态服务。
如果在调用期间,ref所执行的服务卸载、或者更新了,那么原来持有的ref机成为无效(invalid),getService(ref)将返回null
服务客户端可以检查处理这种情况,来实现服务的动态性的同时兼顾到性能问题。
-----------------------------------
再看第2个问题:为什么需要ungetService?
先看看网上的典型的解释:
BundleContext.ungetService()释放服务
ungetService()的参数是服务引用,告诉框架我们不再用这个服务了。框架会记住有几个bundle在使用某个服务,如果数目为0,则提供服务的bundle停止时,可以安全地移除该服务。
ungetService()要放在finally块中!保证即使抛出异常,仍然会执行。
——注意这一步是必须的!不像serviceRegistration.unregister()
在回答这个问题前,需要做几个试验:
1、客户端Bundle B通过 Import-Package方式显式引用服务端Bundle A的Package,并持有其对象;
2、客户端Bundle B持有服务端Bundle A的一个服务对象;(但并不导入Bundle A的任何package----想象一下LogService的实现模块)
3、客户端Bundle B使用服务端Bundle A的一个服务对象,并在使用完成后ungetService;
4、对于以上情况,分别uninstall Bundle A,观察一下OSGi缓存(比如Felix的cache目录)的情况;
对于场景一,Bundle B显式依赖Bundle A的情况,uninstall Bundle A的结果,实际上BundleA并不会从cache这删除。
这点很容易理解,因为BundleB和BundleA的ClassLoader形成了静态的委托关系(更准确的术语是org.osgi.framework.wiring.BundleWire),
当这种ClassLoader之间的委托关系建立以后,Bundle的状态才会变成已解析(RESOLVED)
Bundle A 的uninstall或者是update操作,不应该导致已经Active的BundleB,重新变成解析失败状态。
设想一下,某一个bundle在线升级了,导致所有受影响的构件都重新停止、再启动,也是一件影响非常大的事情。
所以BundleA此时是被假删除的。
OSGi框架另外提供了手动同步这种情况的方法,就是:org.osgi.framework.wiring.FrameworkWiring接口上的
void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners);
这时候,BundleB首先被Stop,然后重新再解析。。当然,这时候找不到Import-Package需要的包了,从而解析失败。
对于场景二、三,无论BundleB是否使用ungetService来释放获取的Service对象,都不影响bundleA 的卸载。
仔细查看Felix的实现代码后,大概的理解是这样的:
ungetService方法是配合 ServiceFactory接口使用的。通过ServiceFactory,你可以为不同的Bundle提供不同的Service对象,
也可以为每一次请求生成不同的Service对象,或者结合HttpSession,为每一个会话分配不同的服务对象。
而且,如果Service对象是重型对象(比如持有一个未关闭的流),那么SeviceFactory中还提供了一个unget的回调方法,
用来在Service对象不再被使用时执行一些销毁资源的动作。
同时,对同一个bundle上服务的使用,框架还会维持一个引用计数,这样做的目的是一、同一个bundle在unget之前
获取的所有服务对象一定是相同的;二、当这个服务对象不再被某个bundle使用的时候,可以回调一下ServiceFactory来释放资源。
但是!!说实话,没看到这样的设计有太大的意义。一则,服务对象通常应该设计为单例对象;二则依靠客户端来调用unget
从而触发ServiceFactory释放资源是极不可靠的。三、服务对象应该是轻量级,而不应该是重量级的。
这是一个鸡肋的设计?