osgi 模块化_OSGi案例研究:模块化vert.x

osgi 模块化

OSGi使Java代码可以清晰地划分为多个模块,这些模块称为捆绑软件 ,可以访问由每个捆绑软件的类加载器控制的代码和资源。 OSGi 服务提供了一种附加的分离机制:接口的用户无需依赖于实现类,工厂等。

以下案例研究旨在使OSGi捆绑包和服务的上述优势具体化。 它采用了一个有趣的Java项目vert.x,并展示了如何将其嵌入OSGi并利用OSGi的功能。

免责声明:我不建议更换vert.x容器或其模块系统。 这主要是在OSGi的使用中进行的案例研究,尽管某些发现应促使对vert.x进行改进,尤其是将其嵌入具有自定义类加载器的应用程序中时。

版本号

vert.x开源项目提供了node.js的JVM替代方案:异步,事件驱动的编程模型,用于以多种语言(包括Java,Groovy,JavaScript和Ruby)编写Web应用程序。

vert.x支持HTTP以及现代协议,例如WebSocketssockjs (与WebSockets 相比 ,它们在更多的浏览器中工作,并且可以更轻松地穿越防火墙)。
vert.x具有分布式事件总线,允许已知为verticles并称为busmods共享代码库vert.x应用程序之间进行传播JSON消息。 busmod是一种特殊的Verticle,它处理事件总线中的事件。 vert.x附带了一些busmod,例如MongoDB的 “ persistor”,用户可以编写自己的。

vert.x的线程模型很有趣,因为每个顶点(或busmod)在其生命周期内都绑定到特定线程,因此,顶点代码无需关注线程安全性。 线程池用于在顶点上分派工作,并且每个顶点必须避免阻塞或长时间运行的操作,以免影响服务器吞吐量(vert.x提供了有效地实现长时间运行的操作的单独机制)。 这类似于CICS事务处理器中的准可重入线程模型。 1个

这里特别受关注的是vert.x模块系统,该系统每个顶点都有一个类加载器,还有称为模块的代码库,它们被加载到使用它们的每个顶点的类加载器中。 因此,除了通过事件总线之外,没有其他方法可以在各个顶点之间共享代码。

vert.x具有出色的文档,包括主要手册java手册 (以及其他语言的手册), 教程和可运行的代码示例

OSGi

如果您还不熟悉OSGi,请阅读我的OSGi简介帖子,但是现在不要再理会该帖子中的链接-您可以随时返回并稍后再做。

在OSGi中嵌入vert.x

我通过几个小步骤来完成此操作,下面依次介绍了这些步骤:将vert.x JAR转换为OSGi捆绑包,然后模块化verticle,busmod和事件总线客户端。

将vert.x JAR转换为OSGi捆绑软件

vert.x手册鼓励用户使用vert.x核心JAR将vert.x嵌入自己的应用程序中,因此将vert.x嵌入OSGi的第一步是将vert.x核心JAR转换为OSGi捆绑包,因此可以将其加载到OSGi运行时中。

我使用了Bundlor工具,尽管其他工具(例如bnd)也可以很好地工作。 Bundlor接受一个模板,然后分析JAR的字节码以产生带有适当OSGi清单标头的新JAR。 请立即参阅SpringSource Bundlor文档以获取有关Bundlor的更多信息,因为在撰写本文时Eclipse Virgo Bundlor文档尚未发布,即使Bundlor项目已转移到Eclipse.org。

vert.x核心JAR的模板如下:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.vertx.core
Bundle-Version: 1.0.0.final
Bundle-Name: vert.x Core
Import-Template:
 org.jboss.netty.*;version="[3.4.2.Final,4.0)",
 org.codehaus.jackson.*;version="[1.9.4,2.0)",
 com.hazelcast.*;version="[2.0.2,3.0)";resolution:=optional,
 groovy.*;resolution:=optional;version=0,
 org.codehaus.groovy.*;resolution:=optional;version=0,
 javax.net.ssl;resolution:=optional;version=0,
 org.apache.log4j;resolution:=optional;version=0,
 org.slf4j;resolution:=optional;version=0
Export-Template: *;version="1.0.0.final"

(此案例研究的模板和所有其他部分都可以在github上找到 。)

这是为JAR依赖的软件包定义有效的版本范围(范围“ 0”表示0或更大的版本范围),这些软件包是可选的还是强制的,以及JAR自己的软件包应为哪个版本出口处。 它还为捆绑软件提供了符号名称 (用于标识捆绑软件),版本和(描述性)名称。 有了这些信息,OSGi然后通过委派包类加载器之间的类加载和资源查找,将包的依赖关系连接在一起。

值得庆幸的网状网络JAR和杰克逊 JSON JAR文件将vert.x核心JAR取决于附带有效的OSGi清单。

为了验证清单是否有效,我尝试在处女座内核中部署vert.x核心软件包。 只需将vert.x核心软件包放置在拾取目录中,并将其依赖项放置在repository / usr目录中,然后启动内核即可。 以下控制台消息显示vert.x核心捆绑包已安装并成功解决:

<hd0001i> Hot deployer processing 'INITIAL' event for file 'vert.x-core-1.0.0.final.jar'.
<de0000i> Installing bundle 'org.vertx.core' version '1.0.0.final'.
<de0001i> Installed bundle 'org.vertx.core' version '1.0.0.final'.
<de0004i> Starting bundle 'org.vertx.core' version '1.0.0.final'.
<de0005i> Started bundle 'org.vertx.core' version '1.0.0.final'.

然后使用处女座外壳,检查线束的接线:

osgi> ss
"Framework is launched."

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
...
89      ACTIVE      org.vertx.core_1.0.0.final
90      ACTIVE      jackson-core-asl_1.9.4
91      ACTIVE      jackson-mapper-asl_1.9.4
92      ACTIVE      org.jboss.netty_3.4.2.Final

osgi> bundle 89
org.vertx.core_1.0.0.final [89]
  ...
  Exported packages
    ...
    org.vertx.java.core; version="1.0.0.final"[exported]
    org.vertx.java.core.buffer; version="1.0.0.final"[exported]
    ...
  Imported packages
    org.jboss.netty.util; version="3.4.2.Final"<org.jboss.netty_3.4.2.final [92]>
    ...
    org.codehaus.jackson.map; version="1.9.4"<jackson-mapper-asl_1.9.4 [91]>
    ...

我还按照以后需要的类似方式将vert.x平台JAR转换为OSGi捆绑软件。

模块化顶点

一个典型的顶点如下所示:

public class ServerExample extends Verticle {

  public void start() {
    vertx.createHttpServer().requestHandler(new Handler<httpserverrequest>() {
      public void handle(HttpServerRequest req) {
        ...
      }
    }).listen(8080);
  }
}

调用start方法时,它将创建一个HTTP服务器,并向该服务器注册一个处理程序,并设置服务器在端口上侦听。 除了处理程序的主体之外,该代码的其余部分都是样板。 因此,我决定将样板分解为一个通用的OSGi捆绑包(org.vertx.osgi),并用包含处理程序和一些等同于样板的声明性元数据的模块化顶包来替换该竖版。 常见的OSGi捆绑包使用白板模式来侦听OSGi服务注册表中的特定种类的服务,基于元数据创建样板,并向生成的HTTP服务器注册处理程序。

让我们看一下模块化的vertical bundle。 它的代码包含一个HttpServerRequestHandler类: 2

public final class HttpServerRequestHandler implements Handler<httpserverrequest> {

    public void handle(HttpServerRequest req) {
        ...
    }

}

它还具有服务属性形式的声明性元数据,这些声明元数据与处理程序一起在OSGi服务注册表中注册。 我可以使用OSGi蓝图服务来执行此操作,尽管我可以使用OSGi声明性服务,甚至可以使用OSGi API以编程方式注册该服务。 蓝图元数据是捆绑软件中的文件blueprint.xml ,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
        
    <service interface="org.vertx.java.core.Handler" ref="handler">
        <service-properties>
            <entry key="type" value="HttpServerRequestHandler">
            <entry key="port" value="8090">
        </service-properties>
    </service>
        
    <bean class="org.vertx.osgi.sample.basic.HttpServerRequestHandler"
          id="handler"/>

</blueprint>

此元数据声明应创建HTTP服务器(通过类型服务属性),向其注册的处理程序以及侦听端口8090的服务器集(通过端口服务属性)。 当org.vertx.osgi捆绑包运行时,这一切都是通过白板模式完成的,如下所示。

请注意,模块化Verticle仅依赖于Handler和HttpServerRequest类,而原始Verticle也依赖于Vertx,HttpServer和Verticle类。 对于那些喜欢单元测试(除了容器内测试)的人来说,这也使事情变得简单得多,因为需要的模拟或存根数量更少。

那么我们现在有什么呢? 将两个包添加到我们之前安装的包中:一个org.vertx.osgi包,它封装了样板代码;一个应用程序包,它代表一个模块化的verticle。 我们还需要一个Blueprint服务实现-从Virgo 3.5开始,Virgo内核内置了一个Blueprint实现。 以下交互图显示了一种可能的事件序列:

在OSGi中,每个捆绑包都有其自己的生命周期,并且通常设计捆绑包时,无论它们相对于其他捆绑包启动的顺序如何,它们都将正确运行。 在上面的示例中,假定的启动顺序为:蓝图服务,org.vertx.osgi包,模块化verticle包。 但是,org.vertx.osgi捆绑包可以在模块化Verticle捆绑包之后开始,并且最终结果将是相同的:将创建服务器,并且在服务器上注册模块化Verticle捆绑包的处理程序,并且服务器设置监听。 如果蓝图服务是在org.vertx.osgi和模块化Verticle捆绑包之后启动的,那么直到蓝图服务启动后,org.vertx.osgi捆绑包才会检测到该模块化Verticle捆绑包的处理程序服务出现在服务注册表中,但是最终结果将再次相同。

github项目包含一些示例模块化verticle的源代码: 基本的HTTP垂直版本 (在8090端口上运行)和sockjs verticle (在8091端口上运行)。 org.vertx.osgi捆绑软件需要更多代码来支持sockjs,而模块化的sockjs verticle除了提供HTTP处理程序外,还需要提供sockjs处理程序。

模块化BusMods

MongoDB持久程序是处理来自事件总线的消息的busmod的典型示例:

public class MongoPersistor extends BusModBase implements Handler<message<jsonobject>> {

  private String address;
  private String host;
  private int port;
  private String dbName;

  private Mongo mongo;
  private DB db;

  public void start() {
    super.start();

    address = getOptionalStringConfig("address", "vertx.mongopersistor");
    host = getOptionalStringConfig("host", "localhost");
    port = getOptionalIntConfig("port", 27017);
    dbName = getOptionalStringConfig("db_name", "default_db");

    try {
      mongo = new Mongo(host, port);
      db = mongo.getDB(dbName);
      eb.registerHandler(address, this);
    } catch (UnknownHostException e) {
      logger.error("Failed to connect to mongo server", e);
    }
  }

  public void stop() {
    mongo.close();
  }

  public void handle(Message<jsonobject> message) {
    ...
  }

}

同样,这里混合了样板代码(用于注册事件总线处理程序),启动/停止逻辑,配置处理以及事件总线处理程序本身。 我对其他版本应用了类似的方法,并将样板代码分离到org.vertx.osgi包中,将处理程序和元数据(包括配置)保留在模块化busmod中。 持久性对MongoDB客户端JAR(mongo.jar)的依赖很方便,因为此JAR附带了有效的OSGi清单。

这是blueprint.xml

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
        
    <service ref="handler" interface="org.vertx.java.core.Handler">
        <service-properties>
            <entry key="type" value="EventBusHandler"/>
            <entry key="address" value="vertx.mongopersistor"/>
        </service-properties>
    </service>
        
    <bean id="handler" class="org.vertx.osgi.mod.mongo.MongoPersistor"
          destroy-method="stop">
        <argument type="java.lang.String"><value>localhost</value></argument>
        <argument type="int"><value>27017</value></argument>
        <argument type="java.lang.String"><value>default_db</value></argument>
    </bean>
      
</blueprint>

请注意,样板配置由处理程序类型和事件总线地址组成。 其他配置(主机,端口和数据库名称)特定于MongoDB持久程序。

这是模块化的MongoDB busmod代码

public class MongoPersistor extends BusModBase
                            implements Handler<Message<JsonObject>> {

    private final String host;

    private final int port;

    private final String dbName;

    private final Mongo mongo;

    private final DB db;

    public MongoPersistor(String host, int port, String dbName)
           throws UnknownHostException, MongoException {
        this.host = host;
        this.port = port;
        this.dbName = dbName;

        this.mongo = new Mongo(host, port);
        this.db = this.mongo.getDB(dbName);
    }

    public void stop() {
        mongo.close();
    }

    public void handle(Message<JsonObject> message) {
        ...
    }

}

该代码仍然扩展了BusModBase,仅仅是因为BusModBase提供了几种方便的辅助方法。 同样,与非模块化等效代码相比,生成的代码更简单,更易于单元测试。

模块化事件总线客户端

最后,我需要一个模块化的Verticle来测试模块化的MongoDB持久性。 这些verticle需要做的就是将适当的消息发布到事件总线。 普通的vert.x垂直版本使用Vertx类获取事件总线,但是我再次使用了Blueprint服务,这次是在服务注册表中查找事件总线服务,并将其注入到模块化垂直版本中。 我还扩展了org.vertx.osgi捆绑包,以便在服务注册表中发布事件总线服务。

模块化事件总线客户端的blueprint.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <reference id="eventBus" interface="org.vertx.java.core.eventbus.EventBus"/>

    <bean class="org.vertx.osgi.sample.mongo.MongoClient">
        <argument ref="eventBus"/>
        <argument type="java.lang.String">
            <value>vertx.mongopersistor</value>
        </argument>
    </bean>
      
</blueprint>

然后, 模块化事件总线客户端代码非常简单:

public final class MongoClient {

    public MongoClient(EventBus eventBus, String address) {
        JsonObject msg = ...
        eventBus.send(address, msg,
                      new Handler<Message<JsonObject>>(){...});
    }

}

旋转一下

1.我已经在git的bundles目录中提供了所有必需的OSGi捆绑 。 您可以通过克隆git存储库来获取它们:

git clone git://github.com/glyn/vert.x.osgi.git

或通过下载git repozip文件

2. vert.x需要Java 7 ,因此请设置一个终端外壳以使用Java7 。确保正确设置了JAVA_HOME环境变量。 (如果现在无法获取Java 7,则将捆绑软件部署到OSGi时会看到一些错误,并且您将无法在步骤8和9中运行示例。)

3.如果您是OSGi用户,只需在您喜欢的OSGi框架或容器中安装并启动捆绑软件,然后跳至步骤8。否则,请按以下方式使用git存储库中的Virgo内核副本。

4.将目录更改为git repo的本地副本中的virgo-kernel-…目录。

5.在UNIX上,发出:

bin/startup.sh -clean

或在Windows上,发出:

bin\startup.bat -clean

6.处女座内核应启动并在其拾取目录中部署各种捆绑软件:

  • org.vertx.osgi捆绑包( org.vertx.osgi-0.0.1.jar
  • HTTP示例模块化org.vertx.osgi.sample.basic-1.0.0.jarorg.vertx.osgi.sample.basic-1.0.0.jar
  • SockJS示例模块化verticle( org.vertx.osgi.sample.sockjs-1.0.0.jar
  • MongoDB持久性示例模块化busmod( org.vertx.osgi.mods.mongo-1.0.0.jar

7.如果要查看现在正在运行的捆绑软件,请从另一个终端启动Virgo Shell:

telnet localhost 2501

并使用sslb命令汇总已安装的捆绑软件。 help命令将列出其他可用命令,而disconnect将使您脱离Virgo Shell。 这是ss命令的典型输出:

...
89      ACTIVE      org.vertx.osgi_0.0.1
90      ACTIVE      jackson-core-asl_1.9.4
91      ACTIVE      jackson-mapper-asl_1.9.4
92      ACTIVE      org.jboss.netty_3.4.2.Final
93      ACTIVE      org.vertx.core_1.0.0.final
94      ACTIVE      org.vertx.osgi.mods.mongo_1.0.0
95      ACTIVE      com.mongodb_2.7.2
96      ACTIVE      org.vertx.platform_1.0.0.final
97      ACTIVE      org.vertx.osgi.sample.basic_1.0.0
98      ACTIVE      org.vertx.osgi.sample.sockjs_1.0.0

lb命令(包括更具描述性的Bundle-Name标头):

...
   89|Active     |    4|vert.x OSGi Integration (0.0.1)
   90|Active     |    4|Jackson JSON processor (1.9.4)
   91|Active     |    4|Data mapper for Jackson JSON processor (1.9.4)
   92|Active     |    4|The Netty Project (3.4.2.Final)
   93|Active     |    4|vert.x Core (1.0.0.final)
   94|Active     |    4|MongoDB BusMod (1.0.0)
   95|Active     |    4|MongoDB (2.7.2)
   96|Active     |    4|vert.x Platform (1.0.0.final)
   97|Active     |    4|Sample Basic HTTP Verticle (1.0.0)
   98|Active     |    4|Sample SockJS Verticle (1.0.0)

8.现在,您可以使用Web浏览器在localhost:8090尝试基本的HTTP示例,该示例应响应“ hello”,或在http:// localhost:8091的SockJS示例应显示一个框,您可以在其中输入一些文本和一个按钮,单击该按钮会弹出一个窗口:

9.如果要尝试(无头的)MongoDB事件总线客户端,请下载MondoDB并在其默认端口上本地启动 ,然后将org.vertx.osgi.sample.mongo-1.0.0.jarbundles目录复制到Virgo的提取目录。 此捆绑包启动后,它将立即向事件总线发送一条消息,并驱动MongoDB持久程序更新数据库。 如果您不想使用MongoDB来检查是否已进行了更新,请查看处女座的日志(在serviceability/logs/log.log )以查看一些System.out行,如下所示,该行确认了发生的情况:

System.out Sending message: {action=save, document={x=y}, collection=vertx.osgi} 
... 
System.out Message sent 
...
System.out Message response {_id=95..., status=ok}

OSGi和vert.x模块化

在本案例研究中,各种示例OSGi捆绑包都依赖于并共享vert.x核心捆绑包。 每个捆绑包都加载在其自己的类加载器中,并且OSGi根据OSGi捆绑包的连接方式控制类加载和资源查找的委派。 以相同的方式,写为OSGi包的顶点可以自由地依赖和共享其他OSGi包。

这与vert.x模块系统大不相同,在vert.x模块系统中,一个verticle依赖的任何模块(除了busmod之外)都被加载到与verticle相同的类加载器中。

OSGi模块系统的优点在于,每个模块的单个副本安装在系统中,并且对于诸如Virgo shell之类的工具可见并且可以由其管理。 它还使占地面积最小。

vert.x模块系统的优点是,在各个顶点之间不存在模块共享,因此编写不当的模块不会无意或有意地泄漏独立顶点之间的信息。 另外,每个使用它的垂直模块都有每个(非busmod)模块的单独副本,因此可以编写模块而不必担心线程安全,因为每个副本仅在其垂直线程上执行。 但是,OSGi用户可能很高兴要求可重用的模块具有线程安全性,并谨慎地管理任何可变的静态数据,以避免线程之间的泄漏。  

更换容器?

当我提出将vert.x嵌入OSGi的话题时, vert.x的负责人蒂姆·福克斯(Tim Fox)问我是否正在编写当前容器的替代品,对此我回答“不是真的”。 我之所以这么说是因为我喜欢vert.x的事件驱动编程模型及其线程模型,它们似乎是“容器”的一部分。 但想更换一对夫妇的vert.x容器方面:模块系统和verticles登记处理的方式。

后来让我吃惊的是,“容器”作为整体实体的概念在模块化系统中可能有点奇怪,最好考虑多个单独的容器概念,然后可以采用不同的方式组合以适应不同的容器用户。 但是,上面看到的类加载和线程模型之间的微妙相互作用表明,包含的不同概念可以相互依赖。 我想知道其他人如何看待“容器”的概念吗?

结论

由于OSGi框架是一个相当严格的应用程序,因此vert.x声称它可以嵌入其他应用程序中的说法已得到验证。

vert.x模块系统虽然未在模块之间提供隔离,但确实在应用程序之间(包括顶点及其模块)提供了隔离,并且使模块的编写无需关注线程安全性。

提出了一个vert.x问题2 ,这应该使vert.x更易于使用自定义类加载器嵌入其他环境。

vert.x可以遵循netty,jackson和MongoDB JAR的示例,并在其核心JAR和平台JAR中包含OSGi清单,以避免OSGi用户不得不将这些JAR转换为OSGi捆绑软件。 我将这个问题留给其他人提出,因为我无法评估在OSGi中使用vert.x的需求。

在OSGi中运行vert.x可以满足一些出色的vert.x要求,例如如何自动化容器内测试(OSGi有许多解决方案,包括Pax Exam,而Virgo有集成测试框架)以及如何开发verticles并将它们部署到vert .x在IDE的控制下(请参阅Virgo IDE工具指南 )。 处女座还提供了许多附带的好处,包括用于检查和管理束和顶点的管理外壳,复杂的诊断程序以及更多其他功能(有关详细信息,请参阅处女座白皮书 )。

该练习还为处女座带来了一些不错的收益。 修复了370253错误 ,这是在Java 7下运行Virgo的唯一已知问题。Virgo3.5依赖于在此环境中中断的Gemini蓝图,因此引发并修复了错误379384 。 我使用了新的基于Eclipse的Virgo工具来开发各种捆绑软件并在Virgo中运行它们。 结果,我在工具中发现了一些小问题,这些问题将适时解决。

最后,在Virgo内核上运行vert.x进一步验证了该内核适合构建自定义服务器运行时,因为现在除了Tomcat,Jetty以及在内核上运行的一两个自定义服务器之外,我们还拥有vert.x。

脚注:

  1. 在IBM的日子里,我曾在CICS开发团队工作过。 SpringSource的一位同事给了我“ CICS做到了!” 我们开始合作后不久就穿了T恤。 旧习难改。
  2. 模块化垂直模块当前需要拦截vert.x的资源查找逻辑,以便可以轻松提供捆绑中的文件。 将此通用代码移至org.vertx.osgi捆绑包会更好,但这需要首先实现vert.x问题161

参考: OSGi案例研究:来自Mind the Gap博客的JCG合作伙伴 Glyn Normington 的模块化vert.x。


翻译自: https://www.javacodegeeks.com/2012/07/osgi-case-study-modular-vertx.html

osgi 模块化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值