zoukankan      html  css  js  c++  java
  • OSGi入门教程

     

     

    网上能找到的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.

     

    1. 在即将打出的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,如果没有上面这一句,激活者的运行就会失败。

     

    1.    打包成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

     

    1. 打包成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’

     
  • 相关阅读:
    linux使用jstack来找出死循环的java代码
    Dubbo的几种序列化协议
    上传新项目到GitLab
    PI Network项目,手把手教快速挖Pi币
    Oracle数据导出、导入
    如何实现表单标题两端对齐
    cookie 操作记录& vuex 中页面刷新 state 数据丢失的问题
    FCSAN存储与服务器关联映射后在服务器端如何识别操作
    修改密码报-bash: !@#***": event not found
    springboot整合logstash
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2517204.html
Copyright © 2011-2022 走看看