网上能找到的OSGi教程,都喜欢在开篇时就教你如何在eclipse里做一个OSGi的东西,就算你跟着做了(如果有足够的耐心),还是不知道OSGi是怎么一回事;更可气的是,这些教程还会配上大段大段的eclipse截图,以达到浪费篇幅的目的。
鉴于这种情况,我只好找本英文OSGi书看了看。这本书上没有eclipse截图,我看时觉得很满意,所以顺便把入门部分微缩一下,给大家做个简短的教程。
Why OSGi?
OSGi用于在同一个JVM内部实现“模块化” (“组件化”)。
首先,它用来实现模块化,提供模块化的一些常见特性:
1. 模块向外只暴露特定的接口,内部实现对外不可见
2. 模块可独立部署
3. 像服务治理一样管理自己模块暴露的接口,包括服务发布、寻找和版本管理
其次,和SOA那种分布式模块化方案不同,OSGi的模块并不会分布在多台机器上,而是部署在同一个jvm进程里的:
1. 每个模块打包成一个JAR
2. 整个应用只需要一个进程,开发、部署会比SOA更高效
OSGi的具体功能有哪些?
官方文档把OSGi的功能分成三大块:
1. Module: 将程序组装成一个一个的“模块”,模块里除了业务逻辑,还有各种元数据
2. Lifecycle:管理模块的生命周期,比如启动和停止模块,并在运行时让模块跟OSGi框架发生关系(比如“热部署”)
3. Service层:把模块暴露的接口当作SOA服务来管理,比如服务的发布和寻找。
实际使用时,你并不需要用上所有的功能。如果你只对模块组织感兴趣,“热部署”、“服务治理”等等你都可以不用。
顺便提一下OSGi的规范与实现。”OSGi Alliance”组织只提供了规范(specification),具体的java实现则由第三方提供。目前主流的实现有:
1. Apache Felix
2. Eclipse Euinox -- Eclipse组织提供的实现。Eclipse还为它提供了相应的IDE Plugin,以快速搭建OSGi应用。估计这也是为什么很多OSGi教程喜欢从Eclipse截图开始的原因。
3. Knopflerfish
OSGi应用举例
闲话少叙,接下来我们直接看代码,看看基于OSGi的模块化是怎么实现的。在下面的例子里,我们会涉及到OSGi模块的服务化、打包和生命周期管理。
Service/Client
在模块化的语境中,一个模块总是在充当服务提供者(Service)或者服务消费者(Client)。所以我们可以先把Service/Client建好:
Service:
public interface HelloService { public void sayHelloTo(String whom); } public class HelloServiceImpl implements HelloService { @Override public void sayHelloTo(String whom) { System.out.println("Saying hello to " + whom); } }
Client:
public class HelloClient { private static HelloService helloService; public void callHelloService() { helloService.sayHelloTo("beckham"); //向Beckham问好 } … }
将Service/Client都打包成模块
接下来我们把Service和Client分别打成jar包,形成OSGi模块。当然,OSGi模块不仅仅是一个jar包,它还要包含一些必要的元数据和辅助类,以被OSGi框架识别和管理。
我们先从Service模块开始。
Service:
1. 先写一个“服务激活者”,用于指定本模块的服务的实现者并发布服务。这个类会在模块启动时被执行
public class ServiceActivator implements org.osgi.framework.BundleActivator{ @Override public void start(BundleContext context) throws Exception { context.registerService(HelloService.class.getName(), new HelloServiceImpl(), null); System.out.println(HelloService.class.getName() + " has been registred as a service"); } ……. }
你可能注意到这里出现了”Bundle”字样。这是因为在OSGi术语中,模块就是Bundle.
2. 在即将打出的jar包的MANIFET.MF中,标识一下本模块(Bundle),指定模块中的哪些类允许被外界使用,并指定本模块的“激活者”:
Bundle-SymbolicName: player.kent.chen.osgi.service Bundle-Version: 1.0.0 Export-Package: player.kent.chen.osgi.service.interfaces Bundle-Activator: player.kent.chen.osgi.service.impl.ServiceActivator
还有一个条目:
Import-Package: org.osgi.framework
这指明了本模块可以依赖哪些外部java package;由于“激活者”在运行时依赖了org.osgi.framework,如果没有上面这一句,激活者的运行就会失败。
3. 打包成jar
把Service的接口、实现、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-service.jar.bundle
Client:
1. 先写一个“激活者”,从OSGi Context中寻找Service
public class ClientActivator implements BundleActivator{ public static HelloService helloService; @Override public void start(BundleContext context) throws Exception { ServiceReference ref = context.getServiceReference(HelloService.class.getName()); helloService = (HelloService) context.getService(ref); HelloClient.setHelloService(helloService); } …… }
2. 在MANIFET.MF中标识一下本模块,指定欲依赖的类,并指定激活者
Bundle-SymbolicName: player.kent.chen.osgi.client Bundle-Version: 1.0.0 Import-Package: player.kent.chen.osgi.service.interfaces, org.osgi.framework Bundle-Activator: player.kent.chen.osgi.client.ClientActivator
3. 打包成jar
把Client类、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-client.jar.bundle
创建launch程序
Service和Client模块都构建好了,最后写一个Main程序把它们集成在一起跑起来(launch it)。
public class Main { public static void main(String[] args) throws Exception { // 创建launch framework,它是OSGi launch的入口. (getFramework()方法的实现请参见附件里的代码文件) org.osgi.framework.launch.Framework framework = getFramework(); // 安装service bundle Bundle serviceBundle = framework.getBundleContext().installBundle(fileToUri("../play-osgi-service-and-client/service/dist/play-osgi-service.jar.bundle")); // 安装client bundle Bundle clientBundle = framework.getBundleContext().installBundle(fileToUri("../play-osgi-service-and-client/client/dist/play-osgi-client.jar.bundle")); // 启动framework和bundles framework.start(); serviceBundle.start(); clientBundle.start(); // 启动client Class<?> clientClass = clientBundle.loadClass("player.kent.chen.osgi.client.HelloClient"); Method method = clientClass.getMethod("callHelloService", new Class<?>[]{}); method.invoke(clientClass.newInstance()); // 最后停止framework framework.stop(); } … }
运行一下这个Main程序,控制台将打印” Saying hello to Beckham”,表明HelloService调用成功。
你的第一个OSGi应用写好了!它组建了模块(Bundle),指定了依赖关系;但这个例子本身还不能充分地展示“模块化”,我们要再看一个例子:
体验一个反例:越过Service的接口直接调用内部实现
OSGi的模块中未显式暴露的类对其他模块来说是不可见的,我们来测试一下。
把client的激活者的代码改一下,让它直接依赖HelloServiceImpl对象,看看会怎么样。
public class ClientActivator implements BundleActivator{ public static HelloServiceImpl helloServiceImpl; @Override public void start(BundleContext context) throws Exception { ServiceReference ref = context.getServiceReference(HelloService.class.getName()); helloServiceImpl = (HelloServiceImpl) context.getService(ref); HelloClient.setHelloService(helloServiceImpl); } … }
运行程序,结果是: java.lang.NoClassDefFoundError: player/kent/chen/osgi/service/impl/HelloServiceImpl . 也就是说, Client在执行时,相关的类装载器装载不到HelloServiceImpl,因为HelloServiceImpl不属于client模块的Import-Package (player.kent.chen.osgi.service.interfaces)
那如果client模块引入了HelloServiceImpl呢? 在MANIFEST.MF中加入一个Import-Package试试:
Import-Package: player.kent.chen.osgi.service.interfaces, org.osgi.framework, player.kent.chen.osgi.service.impl
运行结果是:
Unresolved constraint in bundle player.kent.chen.osgi.client: missing requirement package; (package=player.kent.chen.osgi.service.impl)
也就是说,虽然client模块中引入了对player.kent.chen.osgi.service.impl的依赖,但并没有任何模块提供了这个package,client的调用依然以异常告终。
试想,如果没有OSGi,仅把client和service两个jar文件放在一起,除非自写Class Loader,否则你无法阻止client里的代码直接调用service的内部实现,继而导致弱封装、强耦合了。这下你可以看出OSGi的模块化的功效了吧?
总结
通过上面的介绍,估计你对OSGi的基本作用已经有了一定的了解,对它如何使用也有了感性的认识。你可以下载附件所含的代码示例,自己体验一下。(这些代码使用Apache Felix作为OSGi的实现,由于Felix对bundle的装载使用了缓存,在每次运行之前,你最好都清除一下前一次运行时产生的felix-cache文件夹)
另外,本文内容比较少,只是“入门的入门”,它只能帮你消除对OSGi的神秘感。要想“完整入门”、或者想全面学习OSGi的强大功能,建议阅读专门的书籍。个人推荐的书是: ‘OSGi in Action’