1.摘要
本文简要讨论了如何看待OSGi,以及在OSGi之上进行开发的一些关键概念,常见问题及编程提示 。
2.如何看待OSGi
JAVA面向对象的编程语言彻底推翻了过程化编程的模型,开启了软件开发的一个新的时代。但是,Java语言并没有过多的关注如何为大规模系统的开发提供支持。在Java技术中,包(Package)的应用将模块化开发推进了一小步,代码文件的归档(JAR文件)发布为模块化部署推进了一小步。为了实现动态扩展的应用系统,降低系统的维护成本,优秀的软件开发人员必须绞尽脑汁为业务系统建立松散耦合的实现模型,采用各种各样的开发模式。尽管如此,系统的需求变更往往是牵一发而动全身。
抛除OSGi的其他特性,OSGi与通常的面向对象的编程的唯一不同是基于OSGi的开发必须建立在模块化编程的概念之上。OSGi通过在JVM之上提供一个基础框架提供动态的模块化系统开发的支持。
OSGi可以说抓住了系统模块化开发需求的关键,在充分利用现有技术(重新建立一套编程语言并提供模块化需求开发支持将是不可想象的)的同时,定义了包(Package)的可见性机制为这一问题提供了一种巧妙的解决方案。OSGi模块化开发支持的实现,其关键是充分利用了JAVA的类加载机制并扩展了JAR元数据清单(META-INF/MANIFEST.MF)来定义模块(Bundle)。因此,在进行OSGi编程开发之前,开发人员必须熟悉JAVA的类加载机制和代码文件归档(JAR)机制。
3.理解类加载机制
关于Java的类加载机制问题可以说是一个热门话题,但是据我所知,大多数Java开发人员对于Java的类加载机制并不是完全理解甚至是并不过多的关注。在此我不打算详细讨论Java的类加载机制,如果你不是很熟悉,可以Google一下:)。
通常,虚拟机启动时首先加载内部类(java.*等等,即:rt.jar),然后加载位于CLASSPATH环境变量下的类代码,然后加载用户的类代码。很多应用系统,如Tomcat在加载完CLASSPATH下的类代码后使用自定义的ClassLoader开始内部的类代码及Web应用的加载过程。
如果开发人员在自己的类定义A中引用其他的类定义B,虚拟机在加载该类时,根据加载该类的ClassLoader的实现机制,通常与虚拟机加载相反的过程查找引用的类。此处常见的两种机制是Self-First和Parent-First。
Self-First机制首先查找当前ClassLoader加载的所有类中是否存在引用的类B,如果没有则交给其Parent ClassLoader进行查找,如果父类加载器加载的类中存在类B则返回,否则代理交给其父类加载器继续查找,依次类推直至根类加载器。
对于Parent-First机制,当前类加载器首先将查找过程代理给其父类加载器进行查找,父类加载器同样继续代理给它自己父类加载器直至根类加载器,如果根类加载器查找到自己加载的类中存在引用的类B则返回,否则,由其子类加载器查找类B,直至当前类加载器。
上图展示了上述两种类加载机制,红色箭头表示当前ClassLoader的类查找操作。在上述两种机制中,类A可以引用类B,如果类A同时引用了类B',而在运行时B'由C5类加载器加载,则会出现ClassNotFoundException。
注意,不同的ClassLoader机制可能不同。
4.常见的类加载问题
下图简单展示了OSGi的类加载机制,Bundle1内部私有的类A引用了Bundle2中公开的类B,类C为Bundle2内部私有。Bundle1的ClassLoader可以直接加载类B,可以通过框架的ClassLoader加载框架实现类(这与具体的OSGi实现框架有关,不同的实现可能不同)。Bundle1的类域为启动类加载器加载的所有类,框架类加载器加载的所有类,Bundle1引用的其他Bundle公开的类及Bundle1内部的类。对于下图来说,Bundle1是无法访问Bundle2的内部类C及Bundle3公开的类D的。
在OSGi的初始开发过程中,最常见的问题就是无法找到类的异常。碰到此类问题时可以根据上述类加载及查找机制逐步分析问题出现的原因。
5.编程技巧与提示
说明:本文中给出技巧与提示部分分别来自于Peter Kriens的aQute网站和Knopflerfish网站。下述给出的技巧与提示其可用性是相对的,开发人员在使用过程中应该根据实际情况进行权衡。
5.1功能接口定义与内部实现分包定义
在确定了一个Bundle提供的功能后,应该将该Bundle对外公布的接口和接口功能的实现使用不同的包定义,充分利用Bundle的包公开和隐藏机制。如,可以将Bundle的功能接口定义在包com.acme.service中,而将其实现定义在com.acme.service.internal包或com.acme.service.impl包中,在Export-Packages中只发布com.acme.service包而隐藏其他的内部包。
BundleActivator的使用
BundleActivator是一个Bundle与OSGi环境交互的桥梁,也是一个Bundle的生命周期的入口。如果一个Bundle不需要获取任何的其他Bundle和OSGi环境的信息,而且也不需要在Bundle启动和停止时进行某些初始化和资源释放操作,则不必定义BundleActivator。
如果Bundle内部功能实现需要获取其他Bundle或OSGi环境信息(如OSGi服务),则可以通过BundleActivator从OSGi环境中接收的BundleContext获取。可以将BundleContext以静态变量的形式存储下来,Bundle内部的其他类可以直接访问而不必为每一个需要的类定义接收BundleContext的构造函数。如下述代码所示:
// Static bundle context public class Activator implements BundleActivator { static BundleContext bc; public void start(BundleContext bc) { Activator.bc = bc; ... } public void stop(BundleContext bc) { Activator.bc = null; // allow for garbage collection ... } }
5.2资源释放与自动的服务注销
如果一个Bundle注册了服务(Service),不必在BundleActivator的Stop方法中手动注销此服务,因为当Bundle停止时OSGi框架会自动注销该Bundle注册的服务。
如果在Bundle启动过程中获取了其他的资源,如线程,内存或窗口句柄等,则必须在此Bundle的Stop方法中置为NULL以便于系统进行垃圾回收。如下述代码所示:
public class Activator implements BundleActivator { public void start(BundleContext bc) { Hashtable props = new Hashtable(); props.put("service.pid", "myfantasticpid"); ManagedService mg = new ManagedService() { public void updated(Dictionary conf) { ... } }; // BundleActivator methods now hidden // from outside access. bc.registerService(ManagedService.class.getName(), mg, props); } // Cleanup up resources in stop method public void stop(BundleContext bc) { Activator.bc = null; if(window != null) { window.setVisible(false); window = null; } } }
5.3不要在BundleActivator中处理耗时的任务
如果一个Bundle在启动过程中需要处理耗时的动作,则不要在BundleActivator的启动线程中处理,否则将会阻塞其他Bundle的启动,从而影响整个OSGi的启动过程。
public void start(BundleContext bc) { new Thread("longTaskName") { { start(); } // I like instance initializer blocks ;) public void run() { ...long operation } }; }
5.4跟踪使用的服务
如果在一个Bundle中需要使用其他Bundle提供的服务,则需要在考虑服务不存在或突然被注销的情形。由于OSGi环境是一个动态的环境,Bundle可以在任何时候安装,启动,停止或卸载,所以使用其他Bundle提供的服务时,必须跟踪这些服务的状态。
如果使用一个服务,可以使用ServiceTracker对该服务进行跟踪。
ServiceTracker logTracker = new ServiceTracker(bc, LogService.class.getName(), null); logTracker.open(); // this might throw a NullPointerException // if the log is not available (LogService)logTracker .getService().log(LogService.LOG_INFO, "Hello log");
如果使用多个服务,可以使用ServiceListener监听该Bundle所关注的所有服务引发的事件,跟踪这些服务的状态。
ServiceListener listener = new ServiceListener() { public void serviceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); switch(ev.getType()) { case ServiceEvent.REGISTERED: ... break; case ServiceEvent.UNREGISTERING: ... break; } } }; String filter = "(objectclass=" + HttpService.class.getName() + ")"; try { bc.addServiceListener(listener, filter); // get all already registered services. ServiceReference[] srl = bc.getServiceReferences(null, filter); for(int i = 0; srl != null && i < srl.length; i++) { listener.serviceChanged( new ServiceEvent(ServiceEvent.REGISTERED, srl[i])); } } catch (Exception e) { log.error("Failed to set up listener for http", e); }
开发人员也可以继承ServiceTracker提供对某一项服务的跟踪。
class HttpTracker extends ServiceTracker { int count; ServiceTracker logTracker; HttpTracker(BundleContext context, ServiceTracker logTracker) { super(context, HttpService.class.getName(), null); this.logTracker = logTracker; } public Object addingService(ServiceReference reference) { try { HttpService http = (HttpService) super .addingService(reference); http.registerServlet("/hello", new MiniServlet(), null, null); log("Added http service #" + ++count); return http; } catch (Exception nse) { log("Failed to add http service: " + nse); } return null; } public void removedService(ServiceReference reference, Object service) { super.removedService(reference, service); count--; log("Removed http service, left #" + count--); } public void modifiedService(ServiceReference reference, Object service) { super.modifiedService(reference, service); log("Modified http service"); } void log(final String message) { LogService log = (LogService) logTracker.getService(); if (log != null) log.log(LogService.LOG_INFO, message); else System.err.println(message); } }