• OSGi起步(OSGi for Beginners)


    一年一度的Ig Nobel prize典礼都都会带来一些非常新颖的观点、发现,这些内容甚至超过了Ig Nobel prizes本身。每位获奖者在做完七个字的总结后,还有机会利用24秒的时间对自己的新观点、新发现进行阐述。

     

    这是一个极其绝妙的主意,这对每位获奖者都是一个需要完成的挑战。

     

    OSGi 是近来业界经常提到的事物,随着Equinox成为Eclipse的顶级项目,Felix被用于SlingGlassfish V3的容器,以及Spring-Modules的发布。但是,很多人都不熟悉OSGi…而且一直不去了解它,也不在意他人正在循序渐进的了解OSGi

     

    至于我,我不是OSGi的鼓吹者,仅仅是喜欢花些时间试图向那些还未了解OSGi的朋友进行讲解。显然,做点少量的范例代码能帮助大家尽快上手。再次强调,我不是OSGi的鼓吹者(也不会站在任何OSGi的立场上。)我仅仅是个普通人,看着OSGi的确不错,并且它的文档也不多才写作本文。当然,如果你很熟悉OSGi,那么不需要阅读本文。(译注:这人太罗嗦了,和唐僧有一拼!)

     

    首先,我给大家做七字总结,然后是24秒的阐述,接着我将用范例来解释这一切。

     

    OSGi 的七字总结和24秒阐述

    OSGi 是一个为Java而设计的组件框架。

     

    24 秒的阐述:OSGi是一个Java框架,该框架能装载以bundle为单位的资源。Bundle能提供服务或响应处理请求,而他们之间的依赖都是被管理起来的,正如一个bundle能从容器中获得它所需要的管理。每个bundle都可以有它自己的内部类路径,所以它可以作为独立的服务单元。所有的这些符合OSGi规范的bundle理论上都可以安装在任何符合OSGi规范的容器中。

     

    闲聊一下,不管我念得如何得快都要花掉24秒,貌似我就只能念这么快了。还缺少什么?喔,这则阐述缺少对为何需要这样一个模块化系统的解释。

     

    为什么需要模块化系统?

    模块化系统为分布式bundle提供了翻译支持(这里的“bundle”超过了“OSGi bundle”的范畴—我习惯使用这一术语来做比喻。)当然,模块话系统的依赖关系是个话题,生命周期也是个话题… …有趣。

     

    所有这些都很重要;所谓翻译并不是使用web serviceEJB翻译依然被强迫通过JNDI方式,jar之间的依赖由并行jar部署来管理(除了JCAWAR,当然他们也有不同的方式进行依赖管理)。

     

    Java EE 是一套解决方案,尽管这些都不是必要的:WARJCA可以包含jar文件,EJB jar通过配置他们的manifest能参考其他jar文件,尽管应用服务器能提供高级的类资源库;一旦你在相同web应用程序或web service中使用不同的版本,JNDI将提供版本检测机制。生命周期是为web应用程序(加载并启动 servlets、上下文监听器)和JCA而存在的,但是EJB3.1可能会有自己的生命周期机制—还不确定。

     

    现在我们知道了,Java EE就是个棒槌可以搞定一切事情。

     

    OSGi JSR-277试图把Java的模块化部署标准化起来,而不是强行往Java EE概念上靠拢,这样也能避免Java EE关于依赖和版本检测以及生命周期方面的弱点。既然本文以OSGi而非模型为主题,那就集中在OSGi上吧

     

    开始OSGi

    简单的讲,运行OSGi是非常简单的,基本上没什么乐趣:寻找一个OSGi容器的实现方案(EquinoxFelixKnopflerfishProSyst),并运行这些容器的启动命令,有点像在运行Java EE的服务器。类似Java EE,每个容器都有不同的启动环境和细小的性能差异;请检查你选择容器的具体信息和选项。为了更加清晰点,我将在本文中采用Equinox

     

    Equinox 是一个OSGi容器,你可以从http://download.eclipse.org/eclipse/equinox/下载它。下载的文件是ZIP格式,解压缩到“eclipse”目录,不必惊讶:EquinoxEclipse内置的OSGi容器。(我将把包含所有Equinox发布的顶级目录“/eclipse”作为$EQUINOX变量。)文档在Equinox快速入门中可以找到,访问$EQUINOX/plugins将显示出很多jar文件:

    /opt/tools/eclipse/plugins> ls
    javax.servlet.jsp_2
     .0.0 .v200706191603.jar                    org.eclipse.equinox.jsp.jasper_1 .0.1 .R33x_v20070816.jar
    javax.servlet_2
     .4.0 .v200706111738.jar                        org.eclipse.equinox.launcher_1 .0.1 .R33x_v20070828.jar
    org.apache.commons.el_1
     .0.0 .v200706111724.jar                org.eclipse.equinox.launcher_1 .0.1 .R33x_v20080118.jar
    org.apache.commons.logging_1
     .0.4 .v200706111724.jar           org.eclipse.equinox.log_1 .0.100 .v20070226.jar
    org.apache.jasper_5
     .5.17 .v200706111724.jar                   org.eclipse.equinox.metatype_1 .0.0 .v20070226.jar
    org.eclipse.equinox.app_1
     .0.1 .R33x_v20070828.jar             org.eclipse.equinox.preferences_3 .2.100 .v20070522.jar
    org.eclipse.equinox.common_3
     .3.0 .v20070426.jar               org.eclipse.equinox.preferences_3 .2.101 .R33x_v20080117.jar
    org.eclipse.equinox.device_1
     .0.0 .v20070226.jar               org.eclipse.equinox.registry_3 .3.1 .R33x_v20070802.jar
    org.eclipse.equinox.event_1
     .0.100 .v20070516.jar              org.eclipse.equinox.servletbridge_1 .0.1 .R33x_v20070816.jar
    org.eclipse.equinox.http.jetty_1
     .0.1 .R33x_v20070816.jar      org.eclipse.equinox.source_3 .3.1 .R33x_r20070918-7n7LECgEKVsLIM1aGBO4b00
    org.eclipse.equinox.http.registry_1
     .0.0 .v20070608.jar        org.eclipse.equinox.source_3 .3.1 .R33x_r20070918-7n7LEClEIdwb-bbP_z--EYAO
    org.eclipse.equinox.http.registry_1
     .0.1 .R33x_v20071231.jar   org.eclipse.equinox.useradmin_1 .0.0 .v20070226.jar
    org.eclipse.equinox.http.servlet_1
     .0.1 .R33x_v20070816.jar    org.eclipse.osgi.services_3 .1.200 .v20070605.jar
    org.eclipse.equinox.http.servletbridge_1
     .0.0 .v20070523.jar   org.eclipse.osgi.util_3 .1.200 .v20070605.jar
    org.eclipse.equinox.http_1
     .0.100 .v20070423.jar               org.eclipse.osgi_3 .3.1 .R33x_v20070828.jar
    org.eclipse.equinox.http_1
     .0.101 .R33x_v20071016.jar          org.eclipse.osgi_3 .3.2 .R33x_v20080105.jar
    org.eclipse.equinox.jsp.jasper.registry_1
     .0.0 .v20070607.jar  org.mortbay.jetty_5 .1.11 .v200706111724.jar


    启动Equinox很简单:在这个目录中,执行java -jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar –console(在控制台窗口),然后你将看到一个命令提示符:

    osgi>


    这就是EquinoxOSGi控制台。从这里开始,你可以安装新的bundle并启动、停止、卸载他们,检查他们的依赖、注册服务,或者其他东西。你尝试到的第一个命令将是“ss”,是“short status”的缩写。如果当前是全新安装,这样的交互看起来是这样的:

    osgi> ss

    Framework is launched.

    id      State       Bundle
    0        ACTIVE      org.eclipse.osgi_3 .3.2 .R33x_v20080105

    osgi>

    这样的显示证明容器是运行着的,并安装了一个bundle。这个bundleid为“0”,这个id有很大的作用,因为你可以使用它来控制bundle的生命周期。

     

    那么,bundle有什么优点呢?bundle提供了生命周期和服务暴露,就像我们在24秒的总结中提到一样。

     

    将一个简单的资料库放入Bundle

    让我们创建一个bundle。我想利用资料库存储信息。我的最初接口看起来是这样的:

    package  repository;

    public   interface  RepositoryService {
            Node put(String path, Node content);
            Node get(String path);

    }

    不是太多,这是一个开始:我可以利用它来存放或提取信息。我的节点类是一个简单的POJO,看起来是这样的:

    package  repository;

    import  java.util.ArrayList;
    import  java.util.Date;
    import  java.util.List;

    public   class  Node {
            Date created 
     =   new  Date();
            String source 
     =   " unknown " ;
            String author 
     =   " unknown " ;
            List contents 
     =   new  ArrayList < String > ();
            String path;

            @Override
            
     public  String toString() {
                    String s 
     =   " node: path= "   +  getPath()  +   " , author= "   +  getAuthor()
                                    
     +   " , created= "   +  getCreated()  +   " , source= "   +  getSource()
                                    
     +   " , data=[ " ;
                    String separator 
     =   "" ;
                    
     for  (String d : getContents()) {
                            s 
     +=  separator  +  d;
                            separator 
     =   " , " ;
                    }
                    s 
     +=   " ] " ;
                    
     return  s;
            }

            
     public  Node() {

            }

            
     public  Node(String content) {
                    getContents().add(content);
            }

            
     public  Node(String content, String context) {
                    
     this (content);
                    setSource(context);
            }

            
     //  .. accessors and mutators go here. 
    }

    这是一个服务实现,它可以独立于OSGi进行实现和测试;到此为止我们还没有涉及到OSGi。要向OSGi迈进就应该有个服务的实现。我最初的代码是以Map为基础:

    import  java.util.HashMap;
    import  java.util.Map;

    import  repository.Node;
    import  repository.RepositoryService;

    public   class  MapRepositoryService  implements  RepositoryService {
            Map
     < String, Node >  data = new  HashMap < String, Node > ();

            
     public  Node put(String path, Node content) {
                    
     return  data.put(path, content);
            }

            
     public  Node get(String path) {
                    
     return  data.get(path);
            }

    }

    然而,一个Map不能很好的实现查询功能,而且很有可能遇到有层次结构的信息。我想用DOM来代替它。所以我现在将引入XOM,并重新实现资源库服务:

    package  repository.impl;

    import  java.util.Date;

    import  nu.xom.Attribute;
    import  nu.xom.Document;
    import  nu.xom.Element;
    import  nu.xom.Elements;
    import  nu.xom.Nodes;
    import  nu.xom.ParentNode;
    import  repository.Node;
    import  repository.RepositoryService;

    public   class  XMLRepositoryService  implements  RepositoryService {
            Document document;
            Element data;

            
     public  XMLRepositoryService() {
                    data 
     =   new  Element( " data " );
                    document 
     =   new  Document(data);
            }

            Node toNode(nu.xom.Element node) {
                    
     if  (node.getAttribute( " source "  ==   null ) {
                            
     return   null ;
                    }
                    Node n 
     =   new  Node();
                    n.setAuthor(node.getAttributeValue(
     " author " ));
                    n.setCreated(
     new  Date(node.getAttributeValue( " created " )));
                    n.setSource(node.getAttributeValue(
     " source " ));
                    Elements e 
     =  node.getChildElements( " contents " );
                    n.setPath(node.getAttributeValue(
     " path " ));
                    
     for  ( int  i  =   0 ; i  <  e.size(); i ++ ) {
                            Element elt 
     =  e.get(i);
                            
     for  ( int  i1  =   0 ; i1  <  elt.getChildCount(); i1 ++ ) {
                                    n.getContents().add(elt.getChild(i1).getValue());
                            }
                    }
                    
     return  n;
            }

            nu.xom.Element getElement(String path) {
                    
     while  ( ! path.startsWith( " // " )) {
                            path 
     =   " / "   +  path;
                    }
                    
     while  (path.endsWith( " / " )) {
                            path 
     =  path.substring( 0 , path.length()  -   1 );
                    }
                    Nodes nodes 
     =  document.query(path);
                    
     if  (nodes.size()  >   0 ) {
                            
     return  (Element) nodes.get( 0 );
                    }
                    
     return   null ;
            }

            
     public  Node get(String path) {
                    Element e 
     =  getElement(path);
                    
     if  (e  !=   null ) {
                            
     return  toNode(e);
                    }
                    
     return   null ;
            }

            
     public  Node put(String path, Node content) {
                    Element oldElt 
     =  getElement(path);
                    
     if  (oldElt  !=   null ) {
                            
     //  need to remove this node! 
                            ParentNode p  =  oldElt.getParent();
                            p.removeChild(oldElt);
                    }
                    StringBuilder pathBuilder
     = new  StringBuilder( " / " );
                    String[] tree 
     =  path.split( " / " );
                    Element node 
     =  data;
                    
     for  (String t : tree) {
                            
     if  (t.length()  !=   0 ) {
                                    Element child 
     =  node.getFirstChildElement(t);
                                    
     if  (child  ==   null ) {
                                            
     // System.err.println("creating new "+t); 
                                            child  =   new  Element(t);
                                            node.appendChild(child);
                                    }
                                    pathBuilder.append(
     ' / ' );
                                    pathBuilder.append(t);
                                    node 
     =  child;
                            }
                    }
                    node.addAttribute(
     new  Attribute( " created " , content.getCreated()
                                    .toString()));
                    node.addAttribute(
     new  Attribute( " source " , content.getSource()));
                    node.addAttribute(
     new  Attribute( " author " , content.getAuthor()));
                    content.setPath(pathBuilder.toString());
                    node.addAttribute(
     new  Attribute( " path " , content.getPath()));
                    Element contents 
     =   new  Element( " contents " );
                    node.appendChild(contents);
                    
     for  (String c : content.getContents()) {
                            Element e 
     =   new  Element( " content " );
                            e.appendChild(c);
                            contents.appendChild(e);
                    }
                    
     // System.out.println(data.toXML()); 
                     return   null ;
            }

            
     public   static   void  main(String[] args) {
                    XMLRepositoryService s 
     =   new  XMLRepositoryService();
                    s.put(
     " /foo/bar/baz "  new  Node( " stuff " ));
                    Node n 
     =   new  Node( " bletch " );
                    n.setAuthor(
     " jottinger " );
                    s.put(
     " /foo/bar/bletch " , n);
                    System.out.println(s.get(
     " foo/bar/baz/ " ));

                    System.out.println(s.get(
     " //foo/bar/baz " ));
                    System.out.println(s.get(
     " //foo/bar/bletch " ));
                    System.out.println(s.get(
     " foo/bar/ " ));
                    System.out.println(s.get(
     " //*[@author='jottinger'] " ));
            }
    }

    这离完美还有很长的距离,不过总算是个好的开始。然而,我们还是没涉及到任何关于OSGi方面的东西—我们只有一个无关紧要的资源库类。然后我们来看看OSGi模块是怎样被构建的。

     

    转移目标:根据依赖关系构造OSGi bundle

    一个OSGi模块是一个.jar文件,在这里,它应该遵循标准的.jar文件规范,除此之外还有一部分信息应该放进manifest文件中。

     

    让我们创建一个简单的模块,首先,最简单的它应该能在启动和结束的时候显示消息,作为内部依赖将引入一个.jar文件。这个jar文件对其他bundle是不可见的-它只是作为我们的范例bundle的资源,在鄙文中这是一个很简单和普通的需求。(是的,不怎么样的例子-我在寻找更简单的,但没找到。)依赖将放在jar中,baselib.jar,这里面只有一个类:baselib.BaseService

    package  baselib;

    import  java.util.logging.Logger;

    public   class  BaseService {
      Logger log
     = Logger.getLogger( this .getClass().getName());
      
     public   void  sayHello() {
        log.info(
     " Hello, world! " );
      }
    }

    接下来要做的是把编译好的文件放进.jar中,这就是baselib.jar

     

    现在,一个OSGi bundle需要一个“activator”,是一个管理bundle生命周期的类。一个activator要实现org.osgi.framework.BundleActivator接口,该接口有两个方法:startBundleContext)以及stopBundleContext)。这些生命周期方法能让bundle注册服务或启动服务,但是在这里它还很简单:

    package  tutorial;

    import  baselib.BaseService;

    import  org.osgi.framework.BundleActivator;
    import  org.osgi.framework.BundleContext;

    import  java.util.logging.Logger;

    public   class  TutorialActivator  implements  BundleActivator {
      Logger log
     = Logger.getLogger( this .getClass().getName());
      
     public   void  start(BundleContext bc) {
        log.info(
     " started " );
        
     new  BaseService().sayHello();
      }

      
     public   void  stop(BundleContext bc) {
        log.info(
     " stopped. " );
      }
    }

    我们不只是把这些东西放进一个.jar文件,然后就能工作了,真不巧(Spring-OSGi可以派上用场,但是这超出了本文的范围。)我们还需要以一种特定的文件集和特定的结构来创建tutorialbundle.jar。首先,baselib.jar应该放在我们新的jar文件的根目录。我们还需要一个MANIFEST.MF文件,它包含了一些OSGi配置和启动信息:

    Manifest-Version:  1.0 
    Bundle-ManifestVersion: 
     2 
    Bundle-SymbolicName: com.theserverside.tutorial.osgi.TutorialBundle
    Bundle-Version: 
     1 
    Bundle-Activator: tutorial.TutorialActivator
    Import-Package: org.osgi.framework
     ; version="1.3.0" 
    Bundle-ClassPath: . , baselib.jar

    这个manifest文件假设相应的jar像这样:

    $ jar tvf tutorialbundle.jar
         
     0  Thu Apr  17   11 : 57 : 14  EDT  2008  META-INF/
       
     391  Thu Apr  17   11 : 57 : 12  EDT  2008  META-INF/MANIFEST.MF
         
     0  Thu Apr  17   11 : 29 : 56  EDT  2008  tutorial/
       
     714  Thu Apr  17   11 : 51 : 02  EDT  2008  tutorial/TutorialActivator.class
       
     902  Thu Apr  17   11 : 15 : 28  EDT  2008  baselib.jar


    这里的要点是我们的manifest文件引入了包(只是一些OSGi框架需要的包)、activator类文件名以及bundle类路径-以逗号隔开的在jar中的资源集。如果我们能再次重复实现这个结构,说明我们已经准备好在Equinox中安全和运行bundle了。

    $ java -jar org.eclipse.osgi_3 .3.2 .R33x_v20080105.jar -console
    osgi> ss

    Framework is launched.

    id      State       Bundle
    0        ACTIVE      org.eclipse.osgi_3 .3.2 .R33x_v20080105

    osgi> install file:///workspaces/osgi/tutorial/tutorialbundle.jar
    Bundle id is 
     4 

    osgi> start 
     4 
    Apr 
     17 ,   2008   11 : 57 : 29  AM tutorial.TutorialActivator start
    INFO: started
    Apr 
     17 ,   2008   11 : 57 : 29  AM baselib.BaseService sayHello
    INFO: Hello
     ,  world!

    osgi> stop 
     4 
    Apr 
     17 ,   2008   1 : 29 : 25  PM tutorial.TutorialActivator stop
    INFO: stopped.

    osgi>


    现在我们知道如何构建bundle了,并知道如何进行相关依赖jar的部署(PS

    后面的资源库类将依赖XOM。)

     

    构建我们的资源库Bundle

    现在我们可以搞定自己的资源库bundle了,需要在Activator中添加些功能:把资源库注册成服务并发布它,所以其他bundle也可以使用我们的资源库进行查询。


    package  repository;

    import  java.util.Hashtable;

    import  org.osgi.framework.BundleActivator;
    import  org.osgi.framework.BundleContext;
    import  org.osgi.util.tracker.ServiceTracker;

    import  repository.impl.XMLRepositoryService;

    public   class  Activator  implements  BundleActivator {

            
     /** 
             * 
     @see  org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
             
     */ 
            
     public   void  start(BundleContext context)  throws  Exception {
                    
     //  register the service 
                    context.registerService(
                                    RepositoryService.
     class .getName(),
                                    
     new  XMLRepositoryService(),
                                    
     new  Hashtable < Object,Object > ());

                    
     //  create a tracker and track the log service 
                    ServiceTracker repositoryServiceTracker  = 
                            
     new  ServiceTracker(context, RepositoryService. class .getName(),  null );
                    repositoryServiceTracker.open();

                    
     //  grab the service 
                    RepositoryService repositoryService  =  (RepositoryService) repositoryServiceTracker.getService();
                    System.err.println(
     " RepositoryService Activated " );
            }

            
     /* 
             * (non-Javadoc)
             * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
             
     */ 
            
     public   void  stop(BundleContext context)  throws  Exception {

                    
     //  close the service tracker 
                    System.err.println( " RepositoryService Deactivated " );
            }
    }

    注意,上面的代码把XMLRepositoryService硬编码进去了,有点不爽。我们可以用Spring、或者Service Provider Interface、或者环境变量、或者—甚至是OSGi青睐的方式,不过这些都超出了本文的范围。让我们开始服务部署,然后我们将揭示如何在其他bundle中调用它。

     

    根据我们的第一个范例bundle,我们的资源库bundle的相关信息将组成manifest文件和形成目录结构。下面是目录结构:

    $ jar tvf repositorybundle.jar
         
     0  Thu Apr  17   14 : 08 : 46  EDT  2008  META-INF/
       
     553  Thu Apr  17   14 : 08 : 44  EDT  2008  META-INF/MANIFEST.MF
         
     0  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/
         
     0  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/impl/
      
     1383  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/Activator.class
      
     2095  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/Node.class
       
     205  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/RepositoryService.class
       
     694  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/impl/MapRepositoryService.class
      
     3823  Thu Apr  17   13 : 57 : 12  EDT  2008  repository/impl/XMLRepositoryService.class
    895924  Thu Apr  17   14 : 03 : 40  EDT  2008  xerces- 2.4.0 .jar
    109318  Thu Apr  17   14 : 05 : 08  EDT  2008  xml-apis- 1.0 .b2.jar
    431568  Thu Apr  17   13 : 54 : 06  EDT  2008  xom- 1.1 .jar
    And the manifest file:

    Manifest-Version: 
     1.0 
    Bundle-ManifestVersion: 
     2 
    Bundle-Name: Repository Plug-in
    Bundle-SymbolicName: repository
    Bundle-Version: 
     1.0.0 
    Bundle-Activator: repository.Activator
    Bundle-Vendor: theserverside.com
    Import-Package: org.osgi.framework
     ; version="1.3.0", 
     org.osgi.util.tracker ; version="1.3.1" 
    Export-Package: repository ; uses:="org.osgi.framework" 
    Bundle-ClassPath: . , xom- 1.1 .jar , xerces- 2.4.0 .jar , xml-apis- 1.0 .b2.jar


    我们在干什么呢?我们在创建一个jar,使用我们之前写好的Activator实现类,以及数个依赖包:Xerces的实现包XOM,以及ServiceTracker API

     

    我们也干了一件有趣的事情:我们把资源库bundle暴露出来。这意味着在OSGi容器中的其他的bundle能引入那些bundle,也可以通过详细的名称来查询服务(这样的话,资源库被注册为“RepositoryService“接口,或者“repository.RepositoryService“。)接下来安装bundle并启动它:


    osgi> install file:///workspaces/osgi/tutorial/repositorybundle.jar
    Bundle id is 
     11 

    osgi> start 
     11 
    RepositoryService Activated

    osgi>

    目前为止一点都不令人兴奋,但是我们已经在利用OSGi基础部件工作了。需要注意的是,我们把接口和实现都放进去了。理想情况下,RepositoryService可以放在自己的jar中,所以我们能分离接口和实现。这并不困难,甚至从bundle的观点看;在activator 中调用bundle接口,你不需要做任何事情,而在bundle实现中,你应该从其他地方导入接口bundle。我们在这里并没有这样干,因为这样做会走很多弯路,相应的也会减缓开发速度。

     

    在其他Bundle中调用我们的OSGi Bundle

    最后步骤是构建例外一个bundle—但它将会找到资源库服务并使用之。

     

    让我们先来看看Bundle Activator。的确很简单,基本上没什么功能:当bundle启动时,它先查找RepositoryService,并在往这个服务中存入数据。它使用stop()机制来实际查找资源库里面的数据并显示在控制台上。这不是一个严谨的测试,但这足以证明流程的行为:

    package  testrepouser;

    import  org.osgi.framework.BundleActivator;
    import  org.osgi.framework.BundleContext;
    import  org.osgi.framework.ServiceReference;

    import  java.util.logging.Logger;

    import  repository.Node;
    import  repository.RepositoryService;

    public   class  SampleActivator  implements  BundleActivator {
            Logger log
     = Logger.getLogger( this .getClass().getName());

            
     /* 
             * (non-Javadoc)
             * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
             
     */ 
            
     public   void  start(BundleContext context)  throws  Exception {
                    ServiceReference ref 
     =  context.getServiceReference(
                    RepositoryService.
     class .getName());
                    RepositoryService lookup 
     =  (RepositoryService) context.getService(ref);
                    Node testNode
     = new  Node( " this is some content " );
                    lookup.put(
     " /foo/bar/baz " , testNode);
                    log.info(
     " /foo/bar/baz stored. " );
            }

            
     /* 
             * (non-Javadoc)
             * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
             
     */ 
            
     public   void  stop(BundleContext context)  throws  Exception {
                    ServiceReference ref 
     =  context.getServiceReference(
                    RepositoryService.
     class .getName());
                    RepositoryService lookup 
     =  (RepositoryService) context.getService(ref);
                    log.info(lookup.get(
     " //*/baz " ));
            }
    }
    The MANIFEST.MF file looks like 
     this :

    Manifest
     - Version:  1.0 
    Bundle
     - ManifestVersion:  2 
    Bundle
     - Name: Repository Sample Plug - in
    Bundle
     - SymbolicName: samplerepouser
    Bundle
     - Version:  1.0 . 0 
    Bundle
     - Activator: sample.SampleActivator
    Bundle
     - Vendor: theserverside.com
    Import
     - Package: org.osgi.framework;version = " 1.3.0 " 
    Require
     - Bundle: repository

    在上面最后一行中。如果你检查RepositoryServicemanifest,字符名字是“repository.”,在这里我们说bundle应该能访问任何被参考的bundle暴露的类,换句话说,一旦我们的资源库bundle暴露了“repository”包,我们的SampleActivator就能够直接导入这个资源库类而不是自己再用包来组织。

     

    我们构建了sample.jar bundle,结构是这样的:

    $ jar tvf ../samplebundle.jar
         
     0  Thu Apr  17   14 : 47 : 48  EDT  2008  META-INF/
       
     421  Thu Apr  17   14 : 47 : 46  EDT  2008  META-INF/MANIFEST.MF
         
     0  Thu Apr  17   14 : 47 : 48  EDT  2008  sample/
      
     1270  Thu Apr  17   14 : 47 : 48  EDT  2008  sample/SampleActivator.class


    注意一个简单的地方:这个bunlde除了activator没什么其他内容了。没有服务实现,甚至接口。

     

    我们安装并开启这个bundle,然后再停止,是否和预期的效果一样:


    osgi> install file:///workspaces/osgi/tutorial/samplebundle.jar
    Bundle id is 
     17 

    osgi> start 
     17 
    Apr 
     17 ,   2008   2 : 49 : 07  PM sample.SampleActivator start
    INFO: /foo/bar/baz stored.

    osgi> stop 
     17 
    Apr 
     17 ,   2008   2 : 49 : 09  PM sample.SampleActivator stop
    INFO: node: path
     = //foo/bar/baz ,  author = unknown ,  created = Thu Apr  17   14 : 49 : 07  EDT  2008 ,  source = unknown ,  data = [ this is some content ] 

    osgi>

    在另一个OSGi容器中运行我们的Bundle

    OSGi 的强大力量之一是容器的“平台无关”,就像Java EE模块能部署到任何兼容的容器中一样。现在我们花点时间来展示我们之前写好的bundle部署到Felix上—ApacheOSGi容器,再看看在其他容器上bundle看起来是什么样子的。Felix首先需要你对当前配置命名,如果你用一样的名字的话它可以重新载入,在这里的范例,我们将把它叫做“tutorial01

    $ java -jar bin/felix.jar

    Welcome to Felix.
    ================= 

    Enter profile name: tutorial01

    DEBUG: WIRE: 
     1.0  -> org.ungoverned.osgi.service.shell ->  1.0 
    DEBUG: WIRE: 
     1.0  -> org.osgi.service.startlevel ->  0 
    DEBUG: WIRE: 
     1.0  -> org.apache.felix.shell ->  1.0 
    DEBUG: WIRE: 
     1.0  -> org.osgi.framework ->  0 
    DEBUG: WIRE: 
     1.0  -> org.osgi.service.packageadmin ->  0 
    DEBUG: WIRE: 
     2.0  -> org.apache.felix.shell ->  1.0 
    DEBUG: WIRE: 
     2.0  -> org.osgi.framework ->  0 
    DEBUG: WIRE: 
     3.0  -> org.osgi.framework ->  0 
    DEBUG: WIRE: 
     3.0  -> org.osgi.service.obr ->  3.0 
    DEBUG: WIRE: 
     3.0  -> org.apache.felix.shell ->  1.0 
    -> install file:///workspaces/osgi/tutorial/tutorialbundle.jar
    Bundle ID: 
     7 
    -> start 
     7 
    DEBUG: WIRE: 
     7.0  -> org.osgi.framework ->  0 
    Apr 
     18 ,   2008   10 : 46 : 37  AM tutorial.TutorialActivator start
    INFO: started
    Apr 
     18 ,   2008   10 : 46 : 37  AM baselib.BaseService sayHello
    INFO: Hello
     ,  world!
    -> install file:///workspaces/osgi/tutorial/repositorybundle.jar
    Bundle ID: 
     8 
    -> start 
     8 
    DEBUG: WIRE: 
     8.0  -> org.osgi.util.tracker ->  0 
    DEBUG: WIRE: 
     8.0  -> org.osgi.framework ->  0 
    RepositoryService Activated
    -> install file:///workspaces/osgi/tutorial/samplebundle.jar
    Bundle ID: 
     9 
    -> start 
     9 
    DEBUG: WIRE: 
     9.0  -> org.osgi.framework ->  0 
    DEBUG: WIRE: 
     9.0  -> module ; bundle-symbolic-name="repository";bundle-version="1.0.0" -> 8.0 
    Apr  18 ,   2008   10 : 47 : 08  AM sample.SampleActivator start
    INFO: /foo/bar/baz stored.
    -> stop 
     9 
    Apr 
     18 ,   2008   10 : 47 : 09  AM sample.SampleActivator stop
    INFO: node: path
     = //foo/bar/baz ,  author = unknown ,  created = Fri Apr  18   10 : 47 : 07  EDT  2008 ,  source = unknown ,  data = [ this is some content ] 
    -> shutdown
    -> RepositoryService Deactivated
    Apr 
     18 ,   2008   10 : 47 : 12  AM tutorial.TutorialActivator stop
    INFO: stopped.


    一个实际的应用程序,类似IRC Bot

    非常清晰的看到资源库范例是如何运行的—但是测试是没什么乐趣的。让我们再把这个测试更进一步,引入一个IRC bot。我们的IRC bot将使用PircBot,因为学习它的API没什么难度,IRC bot将加入某个IRC网络(irc.freenode.net"#pircbot"频道)的频道,将响应两个外部命令:~set~get~set将获取一个路径和一些文字,并把文字加入到路径中;而~get将从路径中获取信息。同时,这个例子极其简单并且也不能达到infobot的水平;那就把这个例子留下来给读者练习,并赋予它更多功能吧。

     

    首先要做的事情是建立一个查询服务的通用方式。的确还没有最好的办法实现!有多种不同模式可选;现在有个简单而不是最好的方式来快速展开。我们先创建ServiceLookup接口,接着为OSGi实现ServiceLookup接口。


    package  service;

    public   interface  ServiceLookup {
        Object getService(String name);
    }

    package  service.osgi;

    import  org.osgi.framework.BundleContext;
    import  org.osgi.framework.ServiceReference;
    import  service.ServiceLookup;

    public   class  OSGIServiceLookupImpl  implements  ServiceLookup {
        BundleContext ctx;

        
     public  OSGIServiceLookupImpl(BundleContext ctx) {
            
     this .ctx  =  ctx;
        }

        
     public  Object getService(String name) {
            ServiceReference ref 
     =  ctx.getServiceReference(name);
            
     return  ctx.getService(ref);
        }
    }

    创建OSGIServiceLookupImpl是很简单的,只是它的构造函数传入了Activator BundleContext

    package  ircbot;

    import  org.jibble.pircbot.IrcException;
    import  org.jibble.pircbot.NickAlreadyInUseException;
    import  org.osgi.framework.BundleActivator;
    import  org.osgi.framework.BundleContext;
    import  service.osgi.OSGIServiceLookupImpl;

    import  java.io.IOException;

    public   class  BotActivator  implements  BundleActivator {
        IRCBot bot 
     =   null ;

        
     public   void  start( final  BundleContext context)  throws  Exception {
            
     try  {
                bot 
     =   new  IRCBot( new  OSGIServiceLookupImpl(context));
                bot.setVerbose(
     true );
                bot.connect(
     " irc.freenode.net " );
                bot.joinChannel(
     " #pircbot " );
            } 
     catch  (NickAlreadyInUseException e) {
                e.printStackTrace();
            } 
     catch  (IOException e) {
                e.printStackTrace();
            } 
     catch  (IrcException e) {
                e.printStackTrace();
            }
        }


        
     public   void  stop(BundleContext context)  throws  Exception {
            bot.disconnect();
            bot.dispose();
        }
    }


    下面的代码全是在创建IRCBot实例,要用到ServiceLookup实现:

    package  ircbot;

    import  org.jibble.pircbot.PircBot;
    import  service.ServiceLookup;
    import  repository.RepositoryService;
    import  repository.Node;

    public   class  IRCBot  extends  PircBot {
        ServiceLookup service;

        
     public  IRCBot(ServiceLookup service) {
            
     super ();
            
     this .service = service;
            setName(
     " OSGIBot " );
        }

        @Override
        
     protected   void  onMessage(String channel, String sender, String login, String hostname, String message) {
            String[] command
     = message.split( "   " );
            
     if (command.length > 1   &&  ( " ~set " .equals(command[ 0 ])  ||   " ~get " .equals(command[ 0 ]))) {
                String path
     = command[ 1 ];

                
     //  we should use a tracker for this! 
                RepositoryService repository =  (RepositoryService) service.getService(RepositoryService. class .getName());
                
     if ( " ~set " .equals(command[ 0 ])) {
                    StringBuilder content
     = new  StringBuilder();
                    
     for ( int  i = 2 ;i < command.length;i ++ ) {
                        content.append(
     "   " );
                        content.append(command[i]);
                    }
                    Node node
     = repository.get(path);
                    
     if (node == null ) {
                        node
     = new  Node();
                        node.setAuthor(sender);
                        node.setSource(
     " irc " );
                    }
                    node.getContents().add(content.toString().trim());
                    repository.put(path, node);
                }
                
     if ( " ~get " .equals(command[ 0 ])) {
                    Node node
     = repository.get(path);
                    
     if (node != null ) {
                        
     int  count = 0  //  will only do two at most, to be polite 
                         for (String content:node.getContents()) {
                            
     if (count ++> 2 ) {
                                
     break ;
                            }
                            sendMessage(channel, sender 
     +   "  " + content);
                        }
                    }
                }
            }
        }
    }


    最后,我们的MANIFEST.MF文件指明了类路径(包含在pircbot.jar中),以及activator名字(“ircbot.BotActivator”),以及对“repository”的外部依赖:

    Manifest-Version:  1.0 
    Bundle-ManifestVersion: 
     2 
    Bundle-Name: IRCBot Plug-in
    Bundle-SymbolicName: ircbot
    Bundle-Version: 
     1.0.0 
    Bundle-Activator: ircbot.BotActivator
    Bundle-Vendor: theserverside.com
    Import-Package: org.osgi.framework
     ; version="1.3.0" 
    Require-Bundle: repository
    Bundle-ClassPath: pircbot.jar
     , .


    安装并开启这个bundle(废话多),它将连接到Freenode并加入#pircbot。注意这里没有对昵称冲突进行处理;你可以自己写点代码搞定,或者修改默认的昵称。这不是一些好代码,人人都可以放任何东西进去,经不住测试

     

    这里并不是说只有IRCBot才能使用资源库。理论上,一个jabber客户端也可以用相同的资源库(可以用相似的代码来处理。)事实上,这就是OSGi的亮点: IRCBot中处理命令的代码能在一个bundle内部独立运行,并且如果需要的话,IRCBot也能很容易的调用适当的bundle管理命令,一旦要求访问资源库,它们能立刻查找资源库服务。

     

    结论

    希望,本文能启发读者去探求OSGi的潜能,并能让读者知道如何开始使用它。



  • 相关阅读:
    10.20stark组件已经完工
    webpack3到webpack4
    app埋点
    postman使用
    phantomjs
    nodejieba中文分词
    爬虫--cheerio
    mysql命令(三)
    mysql学习(二)
    mysql安装登录
  • 原文地址:https://www.cnblogs.com/sunwei2012/p/1913825.html
Copyright © 2020-2023  润新知