网上能找到的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都打包成模块

接下来我们把ServiceClient分别打成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程序

ServiceClient模块都构建好了,最后写一个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的依赖,但并没有任何模块提供了这个packageclient的调用依然以异常告终

 

试想,如果没有OSGi,仅把clientservice两个jar文件放在一起,除非自写Class Loader,否则你无法阻止client里的代码直接调用service的内部实现,继而导致弱封装、强耦合了。这下你可以看出OSGi的模块化的功效了吧? 

 

 


 

总结

通过上面的介绍,估计你对OSGi的基本作用已经有了一定的了解,对它如何使用也有了感性的认识。你可以下载附件所含的代码示例,自己体验一下。(这些代码使用Apache Felix作为OSGi的实现,由于Felixbundle的装载使用了缓存,在每次运行之前,你最好都清除一下前一次运行时产生的felix-cache文件夹)

 

另外,本文内容比较少,只是“入门的入门”,它只能帮你消除对OSGi的神秘感。要想“完整入门”、或者想全面学习OSGi的强大功能,建议阅读专门的书籍。个人推荐的书是: ‘OSGi in Action’

posted on 2012-05-24 15:41  chenjianjx  阅读(5615)  评论(2编辑  收藏  举报