Home 文章 Java基础 服务器端OSGI简介

feedsky
抓虾
google reader
my yahoo
服务器端OSGI简介 E-mail
User Rating: / 0
PoorBest 
作者是 DanielRubio-070327   
2008-04-25 22:55:01

摘要

  1999年成立的OSGi联盟向Java市场的嵌入式设备领域迈出了第一步。几年之后,它进一步扩展到了Java桌面项目,为Eclipse开源IDE项目的模块化和可扩展性发展提供了重要基础。现在,随着面向服务时代的来临,OSGi正在向最新的Java领域——服务器端进军。

  与技术行业中的其他许多联盟一样,OSGi的最佳实践和开发策略也是由它的许多成员公司统一制订的。在OSGi涉足服务器端的举动下,许多重要的Java供应商都开始围绕OSGi的蓝图调整其SOA产品线,BEA自己的microService架构(mSA)就是一个典型的例子,它依赖于OSGi的底板组件。

  本文解释了OSGi涉足Java/SOA服务器端领域的原因,包括Java供应商针对OSGi调整其SOA所获得的主要收益和受到的主要约束。本文深入分析了以下内容:从开发人员和架构师的角度来看,与OSGi的合作到底构成了什么,以及从更广的平台的意义来说,未来OSGi将在Java领域中扮演何种角色。

OSGi的关键概念

  随着许多OSGi原则成功地应用于从智能电话到IDE(Integrated Development Environment)等各个领域,用几句话来总结OSGi的所有方面是很困难的;然而,下面这些关键概念应该足以阐明OSGi在Java生态系统中的总体位置:

  • 环境和框架:OSGi通常被归类为Java框架。但是,这个词在Java世界中经常被滥用,因此不要认为它也是用于简化Java Web开发和Java测试的一种框架(存在数百个这样的框架)。OSGi超越了这一界线,有效地提供了一个专门的环境,用于促进应用程序的模块化,使其成为许多更小、更易于管理的部分。
  •  JVM伙伴:因为OSGi扎根于嵌入式市场,所以OSGi的重心在于提高Java的“最低公分母”Java Virtual Machine这一点也就不足为奇了。虽然JVM已经发展了十多年,但是在某些方面——如系统服务和动态装载——它已经与一些垂直行业的期望曲线有了很大差距,给像OSGi这些有创新精神的组织提供了切入点。
  • Java封装程序包:当然,环境或框架都必须拥有自己的封装模型,所以就像您可能在Java项目上使用Java EE应用程序或更通用的JAR(Java Archives)时已经习惯处理WAR(Web Archives)和EAR(Enterprise Archives)那样,OSGi也是如此,它也拥有自己的名为“程序包”的封装模型。请不用过多考虑程序包的组成——稍后我们会讲到——目前只需要知道“程序包”这个术语是与OSGi紧密联系的。

  请记住这只是整个OSGi业务范围背后一个很小的概念。现在让我们来看看OSGi为服务器端带来了什么。

服务器端的OSGi:SOA和复杂性

  如果看一下服务器端Java市场的当前形势,就会注意到企业向实现面向服务架构(SOA)的重大转变。虽然这种转变分为巨大变更的和自然渐进两种形式,但无可否认SOA已经带来了建和部署应用程序的新方法,其结果是重新考虑支持这一架构的相同基础架构。

  看一下企业为实现SOA所使用的基础架构就会发现它是Java EE容器、轻量级框架和为此目的采用的中间件的混合体。这个基础架构本身没有问题,但是部署这种软件的方法倾向于单一和极端,它在一定程度上违反了SOA的灵活性和可重用性原则。

  大部分Java应用程序在独立状态下都是相当易于管理的。当这些表面上简单的部分置于生产环境中,并且与其他Java部件或者(如果愿意的话)企业的面向服务的“云集”共同协作时,真正的问题就开始出现了。企业Java中间件和JVM在这些情况下的工作方式就是如前所述的极端方法,这就产生了几个显而易见的、令人头痛的问题:

  • 膨胀的内存占用:由于一些Java应用程序部署根据企业设置需要大至1或2GB的内存(有时甚至更多)才能正常运行,所以会在服务器端产生资源黑洞,其大小与需要部署在单个服务器实例中的应用程序的数量成正比。
  • 类/版本冲突:各种软件的发展演化是此情况下的另一个困境。在同一把伞下,一切都被装载了——JVM和类路径——在单个服务器实例中部署的应用程序越多,应用程序使用不同库(JAR文件)的可能性就越大。结果就是造成类装载冲突和版本冲突。
  • 重复或多余的部件:所有应用程序的拥有者在提供正确执行所必需的依赖关系时都要谨慎入微。然而,一旦将应用程序部署在生产环境中,如果其他一些应用程序不满足某些依赖关系或者一个应用程序实际使用了百分之百的依赖关系(创建者预定的),那么要想中止它,将会很困难。

  如果说有一般解决方案,那就应该是动态装载,该机制可确保应用程序构建块在不工作时被终止,从而将资源消耗限制在那些核心部分正常运行所需的水平。当OSGi推出时,因为它来自于资源缺乏的嵌入式市场,所以它正好提供了这么一种极其有效的按需安装、启动、停止、更新和卸载模块的方法。

   借助此方法,OSGi在JVM(当前还没有这种动态装载和版本特性)和更重量级的Java企业应用程序(它们那更加复杂的依赖关系经常带来一种折磨)之间创建一种极好的协作。BEA's microService架构依靠OSGi作为其底板组件,这意味着BEA产品线将均匀地分布于众多的OSGi程序包中,从而有效地改进了大量部件之间的交互方式,并能限制部署BEA产品的完整的堆时可能出现的版本和类装载冲突。

  再说一下,本文中使用的OSGi含义是经过一定简化的,但是这种更高层面的视角应该足以领会OSGi在试图解决什么。下面该来看看是OSGi内部移动组件的构成:它的环境和及对应的程序包。

OSGi工作组件:环境和程序包

  OSGi环境提供了必要的黑盒来利用如前所述的许多特性。我此时选择使用黑盒这个词是因为环境的创建完全由OSGi供应商/提供者负责,Java EE容器也由供应商以大体上相同的方式负责。开发人员或架构师唯一要关心的就是理解OSGi内部的部署和编程模型,它们同样能够跨您所选择的任何OSGi环境使用,这一点与Java EE具有惊人的相似之处。

  对于已经从Java EE容器迁移到Spring等轻量级框架的用户,也不要因为Java EE容器和OSGi之间最新的比较关系而气馁。事实上,一些相关的工作正在进行之中,以便集成OSGi与Spring的功能。随着本文的深入,您会发现您的Spring项目也会从OSGi中受益。在OSGi中,环境、实现、运行时、容器和框架这些术语经常交替使用,这是经过考虑的。当这些专门词汇经常用来指由OSGi供应商/提供者分发的软件的同一部分时,请保持开放的心态。

  开始探索OSGi环境之后,您可能会惊讶地发现同样的环境不仅能够适用于智能电话等小型设备,也能够在桌面应用程序管理和服务器端项目中应用。其原因是OSGi部署和编程模型同样可以应用于所有这些不同的情况中,这就引入了OSGi最具体的部分:程序包。领会程序包的概念是理解OSGi的关键,因为作为开发人员或架构师,您将时常要直接处理它。这里有个实际的例子,可用来简要说明程序包的概念。

  程序包是OSGi使用的封装格式,也常称作模块,它包含了Java类和资源以及其他OSGi底层环境使用的处理指令——封装的结构与JAR文件的结构非常相似。这就产生了一个问题:“如果OSGi程序包的封装方式与JAR文件一样,那它为何如此特殊呢?”或者还可以这样问:“到底为什么要遵照OSGi程序包的JAR格式重新封装现有的所有应用程序的JAR文件呢?”

  class loader graph

  图1. OSGi通过许多程序包中的各种类构造出的类装载器图

  当它推出时,通过传统的Java类路径还不能获得OSGi程序包的JAR文件——或者简单地说,是OSGi模块(Java类和其他应用程序资源)——的内容。它们被委托给OSGi环境,后者会创建一种针对在自身部署的每一个OSGi程序包JAR文件所含全部底层类和资源之间的依赖关系解析的类装载器图。

  类装载器图的这个概念的特殊性有几个原因。其一,它限制了应用程序执行时在任一单个点装载的Java类和资源的数量。另外,依靠图模型,OSGi环境中运行的任何应用程序都能够根据需要自动创建、合并或销毁分支以满足类的依赖关系,给在开始所提到的动态安装、启动、中止、更新和卸载的功能让路。同样重要的是,因为OSGi程序包由普通的Java类和资源组成,所以OSGi环境对于底层应用程序逻辑是不可知的,因此您可能遇到的OSGi程序包范围很广,从Eclipse IDE插件、XML语法分析器、基于控制台的应用程序、servlet到其他一些变量。

  了解了这一背景之后,我们来看一下实际的OSGi程序包。由于本文是OSGi程序包的介绍,这里给出的例子将尽可能的精简。但通过这些例子,可以试着外推这些相同的原则如何应用于企业空间中更完善的程序包,比如Logging包、Servlet或其他变量。

  我们现在要创建的程序包被设计成飞行运输服务,其他OSGi程序包可以轮流使用它获得某城市的座位可用性。

package intro.carrier;
import java.util.Properties;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;
import intro.carrier.service.FlightService;
/**
 * This class implements a simple bundle that uses the bundle
 * context to register international flight availability service
 * with the OSGi framework. The flight service interface is
 * defined in a separate class file and is implemented by an
 * inner class.
**/
public class Activator implements BundleActivator
{
    /**
     * Implements BundleActivator.start(). Registers an
     * instance of a flight service using the bundle context;
     * attaches properties to the service that can be queried
     * when performing a service look-up.
     * @param context the framework context for the bundle.
    **/
    public void start(BundleContext context)
    {
        Properties props = new Properties();
        props.put("Seating", "International");
        context.registerService(
            FlightService.class.getName(), new FlightImpl(), props);
    }
    /**
     * Implements BundleActivator.stop(). Does nothing since
     * the framework will automatically unregister any registered services.
     * @param context the framework context for the bundle.
    **/
    public void stop(BundleContext context)
    {
        // NOTE: The service is automatically unregistered.
    }
    /**
     * A private inner class that implements a flight service;
     * see FlightService for details of the service.
    **/
    private static class FlightImpl implements FlightService
    {
        // The set of cities contained in the flight service
        String[] c_flight =
            {"mexico city", "paris", "london", "sao paulo", "toronto"};

        /**
         * Implements FlightService.checkAvailability(). Determines
         * if the passed in city is available 
         * @param flight the flight to be checked.
         * @return true if the flight seating is available,
         *         false otherwise.
        **/
        public boolean checkAvaiability(String flight)
        {
            flight = flight.toLowerCase();

            for (int i = 0; i < c_flight.length; i++)
            {
                if (c_flight[i].startsWith(flight))
                {
                    return true;
                }
            }
            return false;
        }
    }
}

  首先,我们引入了许多org.osgi.framework类,它们几乎是每一个OSGi程序包的要素。接着,我们声明了两个initial方法,它们也是OSGi的关键组件:start和stop。顾名思义,从底层环境调用这两个方法对OSGi程序包或者进行注册,或者进行注销。剩余的代码是一个纵切的内部类,作为服务逻辑。没有什么复杂的——只是几个为我们的目的服务的硬编码的值和一个在下面这个接口上的依赖关系:

package intro.carrier.service;
/**
 * A simple service interface that defines a flight service.
 * A flight service simply verifies the existence of seating.
**/
public interface FlightService
{
    /**
     * Check for the availability of a flight.
     * @param flight the flight to be checked.
     * @return true if the flight seating is available,
     *         false otherwise.
    **/
    public boolean checkAvailability(String flight);
}

  就Java代码而言,这些就已经完成了。现在,我们来举例说明需要伴随前面的Java类的OSGi描述符:

Bundle-Name: International Flights
Bundle-Description: A bundle that registers International Flight service
Bundle-Vendor: Apache Felix
Bundle-Version: 1.0.0
Bundle-Activator: intro.carrier.Activator
Export-Package: intro.carrier.service
Import-Package: org.osgi.framework

  这些名称和描述参数是自解释的。Bundle-Vendor参数指出为了进行部署将要使用的OSGi实现,此处指的是名为Felix的Apache Software Foundation的OSGi实现。Bundle-Version域只是表示我们的第一个程序包迭代1.0.0。

  应当注意一下剩余的域。Bundle-Activator指出包含实际的激活逻辑的类——start和stop方法——而Export-Package指出程序包中的哪些包将对于其他OSGi程序包可用。此处,我们表明我们的服务包是可以访问的。最后,Import-Package用来指出为了成功部署程序包而需要装载的依赖关系。

  Java代码编译采用和所有其他Java源文件一样的方式完成,只要OSGi实现在编译类路径中可用,而使用Java SE包含的标准jar工具就可以进行OSGi程序包的实际创建。最终封装的OSGi程序包将拥有如下面清单所给出的一个目录结构:

+-|
  |
  +-META-INF-+
  |          |+MANIFEST.MF
  |
  |
  +-intro/carrier-+
  |               |+Activator$1.class
  |             |+Activator$FlightImpl.class
  |                        |+Activator.class
  |
  |
  +-intro/carrier/service-+
                          |+FlightService.class

  该过程有些冗长和机械,所以我们不会举例说明我们的介绍性的应用程序中所用的剩余的程序包。您可以查阅该示例下载代码,它包括一个自动构建脚本,使大量示例OSGi程序包的创建变得容易。好消息是封装OSGi程序包几乎总是一次完成,就像JAR文件;大多数情况下,这会由第三方提供者来完成。还有就是,目前已经有一些大型OSGi程序包库可以提供从JMX和RMI到XMLRPC的一切东西,它们对于您的项目都是现成可用的。Oscar Bundle Repository就是其中之一。

  您既然已经理解了OSGi程序包,我们就来使用程序包实际运行和部署一个应用程序。

运行和部署OSGi应用程序

  我们现在要创建的应用程序由四个OSGi程序包组成,其中两个包含业务逻辑类或服务,其余的包含一个监控服务(监听OSGi环境中发生的事件)和一个最终程序包(包括服务调用请求和以基于文本的格式返回结果的逻辑)。再说一次,请记住OSGi程序包能包含的类的多样性。我们的程序包包含服务器逻辑和客户端逻辑,以及辅助的监控功能。查阅示例下载代码,您会有此感觉,会了解我们能够多么容易地将数据库逻辑或一些其他支持功能也包括其中。

  运行和部署OSGi程序包高度特定于您要针对的实现。在这点上,您可能遇到从为了运行和部署OSGi程序包的特殊GUI到命令行工具的一切情况。因为我们将针对Apache的OSGi Felix实现构建OSGi程序包,所以下列步骤说明了从此实现的命令行进行的部署过程。

  在工作站上安装了Felix 1.0之后,请确定将示例下载中创建的所有OSGi程序包复制到名为bundle的Felix子目录中。接着,转到Felix的顶层目录并调用以下命令:java -jar bin/felix.jar。之后,会有Enter profile name:的提示。根据喜好输入名称;这对于我们的示例并非关键。如果是在*nix站上,Felix将在您的主目录/.felix目录下以此名称创建一个子目录来放置安装的OSGi程序包。

  输入概要文件名称之后,您将在命令提示中看到首先装载的OSGi程序包。至此,您应该看到如下的事件序列:

[Assuming inside top level Felix directory]
[web@ws_osmosis felix-1.0.0]$ java -jar bin/felix.jar
 
Welcome to Felix.
=================
 
Enter profile name: introtutorial
 
DEBUG: WIRE: 1.0 -> org.osgi.service.packageadmin -> 0
DEBUG: WIRE: 1.0 -> org.osgi.service.startlevel -> 0
DEBUG: WIRE: 1.0 -> org.ungoverned.osgi.service.shell -> 1.0
DEBUG: WIRE: 1.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 1.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 2.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 2.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 3.0 -> org.osgi.service.obr -> 3.0
DEBUG: WIRE: 3.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 3.0 -> org.apache.felix.shell -> 1.0
->

  现在,您正与OSGi环境交互。此时值得讨论的是OSGi程序包可能的状态。active状态指程序包在前面提到的实际的类装载器图中;installed状态指程序包准备好了或者预备好包含到类装载器图中;resolved状态指因为必需的依赖关系,程序包被动态装载。让我们直接看一下这些状态。输入命令ps查看安装的程序包:

-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.0.0)

  上面的清单表明在环境中Felix核心程序包是活动的。现在,如下面清单所示安装示例应用程序程序包:

[Assuming sample bundles are located in Felix bundle sub-directory] -> install file:bundle/listener.jar Bundle ID: 4 -> install file:bundle/carrier.jar Bundle ID: 5 -> install file:bundle/carrier2.jar Bundle ID: 6 -> install file:bundle/userdesk.jar Bundle ID: 7 -> ps START LEVEL 1    ID   State         Level  Name [   0] [Active     ] [    0] System Bundle (1.0.0) [   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0) [   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0) [   3] [Active     ] [    1] Apache Felix Bundle Repository (1.0.0) [   4] [Installed  ] [    1] Service listener (1.0.0) [   5] [Installed  ] [    1] International Flights (1.0.0) [   6] [Installed  ] [    1] Domestic Flights (1.0.0) [   7] [Installed  ] [    1] Service Tracker-based flight client (1.0.0)

  在这最后的清单中,请注意程序包显示(ps)现在是怎样为所有四个应用程序程序包显示一个Installed状态的。启动6号程序包Domestic Flights,然后启动/调用7号程序包查询一次飞行。

-> start 6
DEBUG: WIRE: 5.0 -> intro.carrier.service -> 5.0
DEBUG: WIRE: 5.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 6.0 -> intro.carrier.service -> 5.0
DEBUG: WIRE: 6.0 -> org.osgi.framework -> 0
-> start 7
DEBUG: WIRE: 7.0 -> intro.carrier.service -> 5.0
DEBUG: WIRE: 7.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 7.0 -> org.osgi.util.tracker -> 0
Enter a blank line to exit.
Enter flight: Seattle
Flight available to :Seattle
Enter flight: Atlanta
No flight available to :Atlanta
Enter flight:
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.0.0)
[   4] [Installed  ] [    1] Service listener (1.0.0)
[   5] [Resolved   ] [    1] International Flights (1.0.0)
[   6] [Active     ] [    1] Domestic Flights (1.0.0)
[   7] [Active     ] [    1] Service Tracker-based flight client (1.0.0)
->

  直接启动两个程序包,客户端程序包正如所料地执行,提示我们某个城市,并用Availability或No Availability的文本消息作出回答。但是请注意5号程序包的最终状态。虽然我们明显没有做任何事来影响它,但它现在却显示Resolved状态。之所以这样,原因是6号和7号程序包依赖intro.carrier.service包,它只存在于5号包中;OSGi只关心解析它的活动程序包所需的,并切换5号程序包后面的状态为Resolved,表示依赖关系被满足了。如果您希望的话,可以试试安装、卸载、启动和中止所有这些为数众多的程序包,这样您就能进一步了解OSGi是如何处理该过程的。随便输入help,会得到所有可用的OSGi命令的清单。

  这个示例飞行应用程序可能似乎没什么价值,但是当您采用同样的原则用于更加复杂的企业环境时——这种环境可能同时装载成百上千的类,甚至不一定需要它们——OSGi区别对待需要装载什么和需要何时完成的概念可以拥有强大的性能。

模糊的未来:OSGi、Java Modules、JSRs和Java EE

  致谢:感谢InfoQ、Peter Kriens、Alex Blewitt和Eric Newcomer对于这些OSGi、JSR和Java EE规范展开的在线讨论。

  当OSGi涉及动态Java装载和Java平台缺少的其他服务时,它所提供的功能将极为有用,但是这并不意味Java标准组或JSR委员会背后的人们已经象他们常说的那样“开车时打瞌睡”。事实上,许多联盟的活动老眼与OSGi的特性有关,举例来说:JSR-291 与OSGi/Java集成有关,JSR-277 是与OSGi作用域相似且独立的Java模块规范,还有范围很宽的 JSR-316,它与Java EE 6即将发行的版本有关。

  顾名思义,模糊的未来是指这些JSR计划的接受程度不同。根据开发的成熟度,OSGi相关的技术——JSR-291——明显比有关Java模块的技术——JSR-277——更加成熟,后面这个计划为Java SE 7的一部分。但是,当人们展望未来,关键的确认部分就会起作用,而以企业为中心的Java EE 6规范——JSR-316——没有提到OSGi相关的特性,因而推迟了对于尚未实现的Java模块规范JSR-277(据说将在Java SE 7中完成)的决策。

  当然,这个模糊的未来确实与Java EE 6有关;JSR 277专家组,即Java模块系统,已经表明将力求实现与JSR 291/OSGi的互操作。总而言之,OSGi是三者中最成熟的联盟,当然这部分归功于它对其他Java联盟的影响和其他Java联盟对它的采纳。所以剩下的问题就是:“Java EE 6和Java模块系统会怎样做呢?它们仍然还停留在图纸上,是对OSGi进行补充还是与之集成?”

示例代码

  您可以从这里下载与本文有关的代码。

  要安装它,请先解压下载的文件并确定您的工作站安装有Java Ant和Apache Felix OSGi。在顶层目录下运行ant all编译所有示例OSGi程序包。所有程序包将会生成在bundles目录中。接着,复制Apache Felix bundle目录(挨着Felix包括的jar文件)下的所有四个示例OSGi程序包,然后返回Apache Felix顶层目录执行:java -jar bin/felix,启动OSGi命令行会话。按照提示,输入您选择的概要文件名称。然后就会看到一个活动的Felix OSGi会话。执行本文所给的命令安装或卸载程序包,或者输入help获得完整的可用操作清单。

结束语

  至今,OSGi联盟已经存在一段时间了,它对许多需要动态Java装载微小细节的部门以及标准Java平台没有覆盖的其他组织都产生了一定的影响。现在随着企业面向服务架构的出现,服务器端Java开发正在向这种同样敏捷和灵活的框架/环境迁移,以提供更健壮的应用程序功能。

  我们回顾了OSGi程序包的组成和创建,并介绍了如何在应用程序中部署和运行OSGi程序包,您应该能更好地理解在服务器端架构中使用OSGi以及OSGi在Java平台已有的和将有的影响整体上带来的益处和限制。

参考资料

最近更新 ( 2008-04-25 22:55:01 )
 
Java家,