《OSGi与Equinox》第二章

 由  池建强 发布

第二章 OSGi基本概念

OSGi联盟①(http://osgi.org)是一个独立组织,其使命是:“致力于创建统一的中间件市场。”这表明它本身就包括一系列规范、参考实现以及测试工具,且都围绕Java中的动态模块化展开。模块化系统形成“服务平台”的基础,继而支持创建并执行松耦合、动态的模块系统。OSGi起源于嵌入式技术领域,延续了其精简的结构,仅仅27个Java类型便构成了OSGi的核心规范。本章我们将学习OSGi中的基本概念,了解这些概念是如何组织起来的。我们将学到:

  • OSGi框架中的关键组成部分及相关操作;
  • bundle结构及生命周期;
  • 服务、扩展以及组件协作。

     OSGi联盟成立之初名为Open Services Gateway initiative,后改为此名。

2.1 bundle环境

OSGi系统由一系列称为bundle的组件构成。OSGi服务平台中执行的bundle彼此独立,通过一种设计精良的方式协同工作。bundle本身是完全自描述的,自行声明其公共的API,自行定义在运行时环境下对其他bundle的依赖,同时隐藏其内部的具体实现。

bundle的作者(生产者)创建了bundle并使其能够被他人所用。系统集成者或应用程序编写者(使用者)都可以通过调用已有的API使用bundle或者编写更多的bundle。在具备充足的、能够解决任何给定问题的功能实现之前,这一切都需不断地进行调整。届时,bundle可以自由地组合、配置,并以此创建出预期的系统。

如图2-1所示,OSGi应用并非是一个自底向上或自顶向下的系统,它仅仅由是一系列bundle构成的集合。同样,在OSGi应用中也不存在主程序,某些bundle会提供代码库,其他bundle会启动线程、基于网络交互、访问数据库,或者采用与其他bundle协作的方式访问硬件设备和系统资源。与此同时,bundle之间是存在依赖关系的,通常情况下,在协作式系统中bundle间是相互平等的。

图2-1 OSGi应用可以看作是由一系列相互依赖的bundle构成的集合

图字翻译: OSGi Application=OSGi应用

基于OSGi的系统是动态的,系统中的bundle在整个应用程序生命周期中可能发生改变。一个bundle在任何时刻都可以被安装、卸载或者更新。为了满足使用,bundle必须做到在卸载时能够进行妥善处理,同样地,其他情况也要考虑,如移除,或者被其他相互协作的bundle替换。

具备这些特性后,模块化系统会变得相当简单,但功能很强,其他系统可以构建在其上。Eclipse作为一个平台或者生态系统之所以取得成功,其背后的秘密就是应用了模块化和OSGi bundle。在任何一个成熟规范的大型系统中,所有的组件不可能都源自同一家厂商,这也是未来的发展趋势。实际上,在一个OSGi系统中,例如Eclipse应用,众多bundle都源自不同厂商——某些开源项目、公司或者个人。OSGi所鼓励并支持的强大的模块化,在很大程度上加快了应用的开发及实施,同时为代码复用提供了很多机会。

2.2 为何选择OSGi?

既然OSGi小巧且简单,那么它有何独到之处呢?若想深入理解,我们需要先了解传统的Java应用。Java系统由类型——接口构成。每一个类都有自己的成员——方法属性 ,并且被组织到包里。Java的包集合中定义了一个全局的类型命名空间,Java语言规范定义了一套可见性规则,用来管理类与其成员之间的交互。如图2-2所示,类与包一般会被压缩成Java压缩包(JAR文件)。Java虚拟机(JVM)会在指定的类路径上顺序地收集所有的JAR包,从而发现并加载类。

图2-2 Java应用

图字翻译: Java Application=Java应用

至此,一切看起来都还不错——包如同模块,借助不同的可见性规则实现信息隐藏。从较低的层面看,这似乎非常合理,但若从系统和协作的角度来看就未必如此了。主要有两个问题:包同模块相比粒度过小;JAR包只是一种代码发布机制,本身并不包含任何运行时语义。

在Java中,类型和成员的可见性规则允许开发人员在单个包中隐藏信息,所以自然可理解为包=模块。但是在实际使用过程中,包不能过大,模块不能过多。依据经验,模块中的代码一般都拥有不同的出处,最好使用更细粒度的包命名方式,以满足日后的重构。将包与模块化混为一谈则与实际经验相违背。

JAR的概念非常有用。作为一种代码发布方式,可以说,JAR在初期对于Java的成功起到了重要的推动作用。开发者们将一些有用的功能放入JAR中,这些JAR可以供其他人使用,借此构建系统。只可惜,JAR包仅仅是一种代码的发布方式,它本身对系统运行时的影响甚微。已经发布的JAR包仅仅被简单地放在类路径上,而对其内容的访问并未施加任何控制。

同时,这些特性也说明,Java在定义和管理依赖方面不提供任何支持。如果没有依赖,模块化是不可能实现的。最终你会获得这样一个系统:众多的JAR包会在类路径中争取自己的位置,JAR包中的内容更多的是与代码的作者相关,而非代码的功能,API也不是非常清楚,JAR之间的关系充其量只是遵守一种较弱的规约。如图2-3所示,这种处理方式最终的结果,将会是一个单独的应用可能会由多个紧耦合的JAR包构成,这些JAR包之间具有多个依赖关系,有的甚至是循环依赖。团队之间的协作与共享会因此受到影响,应用更新也会受到阻碍。

图2-3 一个单独的应用

图字翻译: Java Application=Java应用

既然如此,是什么让OSGi变得更加出色呢?其实还是Java,不是吗?的确如此。OSGi是基于Java基本机制(如前所述)构建的,而且又增加了一些关键特性。其中很重要的一点是,在OSGi中谈论的焦点更多的是bundle而非JAR。bundle的具体实现形式仍然是JAR,但是在JAR的基础上增加了身份标识和依赖关系信息;也就是说,bundle是一种自描述的JAR。这种简单的做法有两个作用:生产者和使用者就能表达契约中各自的权利与义务,与此同时也可以根据这些契约,在运行时环境下获取所需信息。

在默认情况下,bundle中的包对其他bundle是不可见的。按照约定,如果其他bundle打算使用某个包中的API,那么包含该API的包必须显式地设置为“exported”(导出),而使用这些API的代码所属的bundle则需要设置一个相匹配的“import”(导入),才能导入相匹配的包。在概念上,这种可见性管理与Java中包的可见性比较起来,虽有类似,但却更易管理,也更为灵活。

OSGi在运行时会遵守这些可见性约束,从而构成了一个松耦合但功能强大的模块化系统的基础。bundle导入某个包仅仅是说明bundle依赖于特定的包,而不关心该包的提供者是哪些bundle。在运行时环境下,bundle的包依赖被解析,由此bundle被连接在一起,基于的规则包括包名称、版本信息以及包匹配属性。这种处理方式有效地消除了类路径地狱(classpath hell)问题,同时大大改善了类加载性能并降低了耦合度。

没有绝对孤立的代码。所有的松耦合都是有代价的。在传统的Java系统中,如果你想使用某些功能,只需要简单地引用所需要的类型。这种紧耦合的方法虽然非常简单,但有其局限性。如果某一种场景需要更强的灵活性,那么这种需求将无法满足。Java社区中已经研究出不少临时或者部分的解决方法,如上下文环境类加载器、Class.forName、“服务”查询、日志打印,等等。所有这些例子都是为了能够实现松耦合组件间的协同运作。

包的导入导出仅是一种静态契约,而服务(services)则用来促进动态协作。服务其实就是对象,用以实现一个契约或一种类型,并且该服务在OSGi中进行了注册。在使用服务时,bundle只需要导入定义该契约的包,同时在OSGi已经注册的服务中找到该契约的实现。需要注意的是,使用服务的bundle既不需要知道服务的具体实现类,也不需要知道实现该服务的bundle——服务的接口和实现有可能会来自不同的bundle。这样做,使得整个系统仍然保持了松耦合式的协作。

服务的动态性是与生俱来的:bundle既可动态地注册,也可动态地注销其所能提供的服务,与此同时,它也可以动态地使用或者释放其所使用的服务。某些bundle提供服务,某些bundle使用服务,还有些bundle既是服务的提供者又是服务的使用者。

在许多方面,OSGi可以认为是Java编程语言的扩展。在开发时,OSGi允许设定包的可见性和依赖关系约束,同时在运行时保证这些约定的有效执行。借助这些约束,可以很容易地构造出由高内聚低耦合的组件组织起来的应用程序。

2.3 bundle剖析

一个bundle是由一系列的自描述文件组成的集合,如图2-4所示。

图2-4 bundle剖析

bundle的内容及相关需求的说明会在其manifest文件中表述,即META-INF/MANIFEST.MF文件。manifest文件遵循标准JAR manifest语法,但增加了一系列特定的OSGi头标识。上图所示的org.equinoxosgi.toast.backend.emergency bundle的manifest信息如下所示:

org.equinoxosgi.toast.backend.emergency/MANIFEST.MF
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.equinoxosgi.toast.backend.emergency
Bundle-Version: 1.0.0
Import-Package: javax.servlet;version="2.4.0",
  javax.servlet.http;version="2.4.0",
  org.equinoxosgi.toast.core;version="1.0.0",
  org.equinoxosgi.toast.core.emergency;version="1.0.0",
  org.osgi.service.component;version="1.0.0",
  org.osgi.service.http;version="1.2.0"
Export-Package: org.equinoxosgi.toast.backend.emergency.internal;
  version="1.0.0";x-internal:=true,
  org.equinoxosgi.toast.backend.emergency.internal.bundle;
  version="1.0.0";x-internal:=true
Bundle-RequiredExecutionEnvironment: J2SE-1.4
Bundle-Copyright: Copyright (c) 2009 equinoxosgi.org
Bundle-Name: Toast Back End Emergency
Bundle-Vendor: equinoxosgi.org

所有bundle的manifest信息中都必须包含Bundle-SymbolicNameBundle-Version头标识。对于OSGi框架、开发者及配置系统来说,这两个头标识的组合确定了唯一的bundle。通过诸如Export-PackageImport-PackageRequire-Bundle等头标识,bundle同样可以表达其模块化相关信息。诸如Bundle-CopyrightBundle-NameBundle-Vendor等附加头标识仅仅用在文档中。本书中,OSGi教程中罗列出的附加头标识,我们都会予以介绍。

在bundle中可以包含Java类、本地库或者其他非可执行文件。bundle的内容和结构完全取决于该bundle的用途及其使用的方式。大多数bundle都是在Java运行时加载代码的。这与JAR的组织结构类似,即以文件组织结构的方式组织包,分包存放Java代码(如org/equinoxosgi/toast/core/Delay.class)。

提供非Java内容(例如源代码、文档或者静态Web内容)的bundle是为适应其应用机制而组织的。举例来说,本地可执行文件和源自其他程序的待访问文件都必须存放在磁盘上而非JAR文件中。OSGi框架的实现(如Equinox)由于支持基于文件夹(folder-based)的bundle,从而简化了上述操作。从本质上来说,基于文件夹的bundle解压后是JAR bundle。

2.4 模块化

2.4.1 导出包

若要访问bundle中的类,该bundle必须将包含该类的包导出;也就是说,在OSGi中,Java依赖关系的单元是Java包。bundle可以导出任意数量的包。通过将某个包导出,就表明bundle能够提供该包,而且也乐意向其他的bundle提供该包。所有被导出的包构成了该bundle的公共API。未被导出的包被认为是bundle中保密的实现细节,这些内容不能被外界访问。这是一个很强大的概念,同时也是OSGi组件模型最吸引人的特色之一。

如下面的manifest片段所示,通过使用Export-Package头标识,bundle导出了部分包。请注意,多个包之间使用逗号分隔,同时每个包指定了一个版本号,并且分别指定版本号

org.equinoxosgi.toast.core/MANIFEST.MF
Bundle-SymbolicName: org.equinoxosgi.toast.core
Bundle-Version: 1.0.0
Export-Package: org.equinoxosgi.toast.core;version=1.2.3,
 org.equinoxosgi.toast.core.services;version=8.4.2

2.4.2 导入包

导出某个包,使这个包对其他bundle可见,但是其他的bundle必须声明对该导出包的依赖。这是使用Import-Package头标识来完成的。

下面的manifest片段展示了bundle如何导入多个包。与导出包类似,多个导入包也是由逗号分隔而成的列表。这里需要注意的是,每一个导入的包都可以指定版本范围。指定的版本范围包括版本上限和下限(依据导出包的版本而定),用以匹配实际需求。对版本、版本范围以及依赖关系管理的讨论会贯穿全书,因为这些内容对开发、维护以及部署模块化系统来说是至关重要的。

org.equinoxosgi.toast.core/MANIFEST.MF
Bundle-SymbolicName: org.equinoxosgi.toast.core
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework;version="[1.3,2.0.0)"
 org.osgi.service.cm;version="[1.2.0,2.0.0)"

2.4.3 需要的bundle

如下述manifest片段所示,使用Require-Bundle头标识,同样可以指定对某个完整bundle的依赖。

org.equinoxosgi.toast.dev.airbag.fake/MANIFEST.MF
Bundle-Name: Toast Fake Airbag
Bundle-SymbolicName: org.equinoxosgi.toast.dev.airbag.fake
Bundle-Version: 1.0.0
Import-Package: org.eclipse.core.runtime.jobs,
 org.equinoxosgi.toast.core;version="[1.0.0,2.0.0)",
 org.equinoxosgi.toast.dev.airbag;version="[1.0.0,2.0.0)"
Require-Bundle: org.eclipse.equinox.common; bundle-version="3.5.0"

通过这种方法,指定bundle与需要的bundle及其所有的导出包直接建立了链接。这种方式虽然方便易用,但是若要在不同的场景下部署bundle,其功能将会减弱。举个例子,如果所需要的bundle没有(或不能)被部署,则该指定bundle便无法被解析,而那些实际所需要的包却有可能从其他可部署的bundle处获得。

需要重构系统,或者需要某个bundle扮演外观(façade)角色(即该bundle是由一系列其他bundle组成的集合)时,使用需要的bundle相当有用。对那些不生成Java代码(所以也就没有导出或者导入包)的模块来说,需要的bundle也能够对它们之间的依赖规则进行定义。

Require-Bundle的历史

历史上,Eclipse项目曾使用Require-Bundle,这是因为初期的Eclipse运行时环境支持使用Require-Bundle。现在Eclipse采用了基于OSGi的技术,对于大部分bundle来说,使用Import-Package将是一个不错的选择。随着人们对灵活性需求的不断增加,这一点是指日可待的。

2.4.4 强化模块化特性

基于这些特性以及对各种需求的综合表述,OSGi框架对各种依赖进行解析,并在运行时将bundle链接在一起。bundle间的协同工作机制以及Java语言的可见性规则,共同强化了OSGi系统的模块化特性。出于管理的需要,OSGi框架为每个bundle提供了属于自己的类加载器,以便对源自不同bundle的类进行区分。当bundle被卸载或者更新时,其自身的类加载器以及由该类加载器加载的所有类均会被摒弃。通过使用分离的类加载器,系统可以在同一时刻加载同一个类的多个版本。这种方式同样加强了标准Java类型的可见性规则,比如在bundle中的包可见性和public、protected 以及 private类型。

2.5 模块化设计概念

基于这些结构,我们应该如何讨论基于OSGi的应用呢?一种方式是参照如下这种抽象的组织结构:

应用>bundle>包>类型>方法

它表示bundle作为一种抽象介于包和应用之间,即bundle比包大但又比应用小。换句话说,一个应用由多个bundle组成;bundle又由包组成;包由类组成;类由一系列的方法组成。类中的方法描述并实现了类的行为。同样道理,应用中的bundle描述并实现了该应用的行为。将应用划分为多个bundle的任务,与将应用划分为类和方法的任务非常相似。

要讨论基于OSGi的系统,另一种途径是讨论分解。在所有层次中,实现高质量设计的关键是采用何种方式进行有效分解。我们一般遵从三个维度分析和衡量分解:粒度、耦合度和内聚。接下来我们将在OSGi环境下逐一分析这些因素。

粒度(Granularity)——粒度用来描述一个bundle中包含多少代码及其他内容。粗粒度的bundle相对易于管理,但扩展性较差且容易造成系统膨胀。细粒度的bundle可以实现完美控制,但也有很多注意事项。选择适合bundle的粒度,需要综合衡量各方因素。粒度过粗未必不好,过细也未必就合适。从某种角度来说,粒度的选择是对内聚和耦合的整体权衡。

耦合(Coupling)——耦合是对bundle之间,以及系统其他部分之间关联关系数量的一种外在描述。耦合度较高的bundle通常依赖于很多其他bundle,一般对其所处的环境有很多特定的要求。相反,松耦合的bundle彼此间独立交互,由此提供了构建应用时的可扩展性和灵活性,即便需求发生变化,松耦合的bundle依然能精确地适应变化,无需引入很多不必要的依赖。

内聚(Cohesion)——内聚是对bundle中的模块与其他bundle之间的关系的一种内在描述。在一个高内聚的bundle中,bundle内部所有的组成单元应该直接定位并聚焦于一个界定清晰且职能单一的领域。低内聚的bundle意味着采用非明确的方式,定义了很多随机化的内容。高内聚的bundle便于测试和重用,你可以仅使用需要的功能,不用额外附加很多不需要的功能。常见的误区是,将一个bundle要么定位为一个完整的子系统,要么定位为某个应用结构中完整的一层,如领域模型或用户接口。高内聚的bundle经常只对某一部分或某个问题提供单一的解决方案。

这些思想并非OSGi所独有,作为良好设计的基本原则,这些思想也是面向对象和敏捷开发的基础。对于OSGi来说,该系统设计旨在揭示并强化一些关键特性,如耦合、内聚以及粒度,从而使得这些抽象概念更具形象化。OSGi鼓励用户按照适当的粒度,将应用划分为多个松耦合、高内聚的bundle。

2.6 生命周期

从根本上讲,OSGi是一种动态技术。在运行时系统中,可以安装、启动、停止、更新和卸载bundle。为了支持这种动态特性,bundle的生命周期必须界定清晰,同时开发者能够通过某种方式监听并跟踪探测bundle的不同生命周期状态(参见图2-5)。

图2-5 bundle的生命周期

图字翻译:

install:安装

update refresh:更新 刷新

INSTALLED:已安装

uninstall:卸载

resolve:解析

refresh update:刷新 更新

start:启动

STARTING:启动中

policy:策略

RESOLVED:已解析

ACTIVE:激活

uninstall:卸载

stop:停止

UNINSTALLED:已卸载

STOPPING:停止中 每个bundle会从已安装(installed)状态开始自己的生命周期。从此开始,如果该bundle的所有依赖均满足,其状态会变为已解析(resolved)。一旦bundle的状态变为已解析,其所有的类可以被加载并运行。如果某个bundle随之启动,并且转换为激活(active)状态,那么它就可以通过激活器(activator)参与其自身的生命周期活动。通过激活器,bundle可以自行完成初始化、获取所需资源,并与系统的其他部分链接在一起。举例来说,当系统关闭时,激活的bundle会被停止。拥有激活器的bundle可以释放其所分配的所有资源。当bundle被停止后,其状态会转换为已解析状态。从这时起,bundle可以被重启或者转换为已卸载(uninstalled)状态,在当前系统中处于已卸载状态的bundle不再可用。

这些状态的变化都由一系列连续的事件驱动。bundle通过监听事件,可以根据具体情况执行不同的动态行为。例如,当一个新的bundle被安装后,其他bundle也许会对其行为非常感兴趣。

当bundle、服务以及框架的状态发生变化时,OSGi框架会分发事件。

  • 服务事件——当服务被注册、修改或者注销时触发。
  • bundle事件——当框架中bundle状态发生改变时触发,例如一个bundle被安装、解析,或者正在启动、已经启动、正在停止、已经停止、未被解析、更新、卸载或者延迟激活。
  • 框架事件——触发时机如下:当框架被启动;触发了错误、警告或者提示事件;框架所用的包被刷新;或者框架的启动级别发生改变。

2.7 协作

如前所述,基于OSGi的系统由一系列自我描述的bundle构成。通过直接引用其他bundle中的类,可以实现bundle间的相互协作。这种简单模式对于所有Java程序员来说并不陌生,但是这样的系统是紧耦合的,恰恰错过了模块化的真正强大之处——松耦合以及动态行为。

若要降低模块间的耦合度,必须要有一套协调机制——一个由第三方扮演的中介,使得相互协作的模块彼此保持适度的距离。在OSGi中,这种典型的机制就是服务注册。Equinox中当然也支持这种机制,它还增加了扩展注册(Extension Registry)。后续章节会涉及此类补充机制,并对该方法进行详细论述。

2.7.1 服务

OSGi中的服务注册扮演了一个类似全局的电子公告牌的角色,主要用来协调如下三方之间的关系:定义服务接口的bundle、实现服务接口并注册服务对象的bundle,以及发现并使用服务的bundle。服务注册使得这种协作以一种匿名的方式进行——提供某种服务的bundle并不知道谁会用这一服务;同样地,使用某个服务的bundle并不知晓该服务的提供者是谁。举例来说,如图2-6所示,Bundle C声明了一个接口,Bundle B实现了该接口并且注册了服务。Bundle A发现并使用该服务,却并不知晓Bundle B的存在,因此也就不存在与Bundle B的耦合。Bundle A仅仅依赖于Bundle C。

图2-6 基于服务的协作

服务是通过使用Java 类来定义的,通常就是一个Java 接口。该类必须是public且其所在的包必须被声明导出。其他的bundle,甚至可能是其自身,实现该服务定义的接口并将其实例化,然后按照已经定义的服务接口名称将该实例注册为服务。实现该服务的类,即接口实现细节,一般不放在导出包之中。

最后,一系列的第三方bundle通过导入包含服务接口的包来使用可用的服务,同时通过在注册服务中按照接口名称来查找服务。当获得了一个已经匹配的服务对象时,使用该服务的bundle可以一直使用该服务,直到使用完毕或者服务被注销。需要注意的是多个bundle可以同时使用同一个服务对象,多个服务对象可以源自一个或多个bundle。

对服务行为的动态特性的管理经常会与bundle的生命周期相互关联。举个例子,当bundle启动时,它会查找自己所需要的服务,同时注册它所提供的服务。类似地,当bundle停止时,该bundle的激活器会注销它所提供的服务,同时释放所有使用的服务。

2.7.2 扩展和扩展点

Equinox扩展注册是一种补充机制,支持bundle内部的相互协作。在这种模型下,bundle可以通过声明一个扩展点(extension point),实现对其自身的扩展和配置。这样的bundle实质上在说:“如果提前告诉我信息,我将……”其他的bundle随后会以扩展的方式,向扩展点提供所需信息。

本书中,我们将以一个可扩展的Web门户为例,通过使用扩展注册来提供或发现不同的action。在该方法中,门户bundle声明了一个actions扩展点并定义了一个契约,即:

“bundle可以通过使用某个路径、标签和类的方式实现IPortalAction接口,进而提供actions扩展,定义门户action。门户会根据指定的路径,为用户呈现指定的标签,这样用户点击标签时,会访问一个特定的URL。请求URL的结果是门户将实例化指定的action类,并将其转化为IPortalAction,同时调用其execute方法。”

图2-7就形象地展示了这一关系。

图2-7 扩展应用

图字翻译:

Web Portal UI : Web门户UI

contributes:贡献

Extension:扩展

implements:实现

instantiates:实例化

calls execute():调用execute()

Tracking:跟踪

扩展及扩展点的关系以XML格式定义在plugin.xml文件中。每一个参与的bundle都有这样一个文件。随着系统中的bundle被解析,它们的扩展及扩展点也随即被加载到扩展注册中,其他bundle均可使用。所有的扩展注册事件通过广播的方式传递给已注册的监听器。通过编程的方式,也能对扩展及扩展点进行管理。

2.8 OSGi框架

在很大程度上,OSGi框架已实现了上述模块化机制。同样地,一个OSGi应用就是一系列运行在OSGi框架中的单个或多个bundle的集合。框架关注并处理所有的依赖解析、类加载、服务注册以及事件管理。

术语

“OSGi框架”、“OSGi运行时”以及“服务平台”等名词在使用过程中经常被互换,通常简称为“框架”、“运行时”或者“平台”。

在运行时系统中,框架会以系统Bundle(System Bundle)的形式存在。OSGi框架以bundle的方式展现,使得我们可以将整体平台统一视为由一系列相互协作的bundle构成的集合。而系统Bundle比较特殊,它包含一个manifest以及导出包,提供并消费服务,同时与其他bundle一样广播并收听事件。

系统Bundle不同于其他的bundle,无法管理它自身的生命周期。它会随着框架启动而自动启动,除非框架停止,否则它会一直处于激活状态。停止系统Bundle会导致框架也随之停止。同样道理,系统Bundle在运行时也不能被卸载,否则会导致框架停止运行。

OSGi系统中的其他bundle会被安装到框架中,随后按需启动。框架中已经安装的bundle集合会一直被保存——当框架停止或重启时,同样的bundle会在新的框架中启动。由此看来,安装并启动bundle只需要完成一次。

有趣的是,框架规范并未说明框架本身如何启动,或者如何安装初始化的bundle集合。按照常理,应该存在一个外部的代理,由它负责bundle的安装、卸载、启动和停止。这个代理可以是一个中央服务提供者、系统整合者、配置代理或者终端用户。如果采用这种处理方式,则将会产生强大的功能,相当于使框架能够适用于更多场景之中。

框架同样提供一些基础的数据管理功能。每一个bundle都有一个属于自己的数据区域,可以根据具体需要使用。只要bundle安装于框架中,写入该区域的数据将会一直保存。

2.9 安全性

OSGi规范将安全性视作一项关键因素。除了标准的Java 2权限外,在整个框架及补充服务中,OSGi特定权限均已被定义。举例来说,当系统在安全模式下运行时,bundle需要不同的权限来注册并查找服务,以及访问相应的属性。

某些专门服务,如条件权限管理服务(Conditional Permissions Admin),会对系统中的权限进行管理。该服务会对每个bundle所含权限进行管理。举例来说,针对拥有第三方数字签名的bundle赋予其特定的权限。此外,针对基于当前用户身份及角色的用户级别,或者应用权限的管理,使用用户管理(User Admin)服务,可简化对上述权限的管理。

OSGi权限模型的真正价值,在于其使用范围贯穿于整个框架和服务集合。

2.10 OSGi框架实现

在撰写本书的过程中,OSGi规范已经经历了4次主要的修订。OSGi已有10年的历史,其间涌现了很多不同的实现。目前一些开源组织以及商业机构已经实现了R4.x版本的OSGi规范。

Equinox——Equinox也许是使用最为广泛的开源OSGi规范实现。Eclipse中的工具、富客户端、服务器端以及嵌入式工程,都采用Equinox作为运行时基础。同时它也是核心框架规范、部分服务规范以及JSR291的参考实现。Equinox的使用需要遵守Eclipse Public License,网址为http://eclipse.org/equinox

Felix——Felix源于Oscar项目, Felix是Apache的开源项目,提供一个框架及部分服务的实现。Felix的使用需要遵守Apache License v2,网址为http://felix.apache.org

mBedded Server——商业化的ProSyst是基于R4.x版本规范的实现,目前已经广泛应用于嵌入式领域。ProSyst提供了一些额外的服务实现。ProSyst的使用需要遵守相应的商业条款,网址为http://prosyst.com

Concierge——Concierge是一个高度精简并优化了的OSGi R3.0规范的实现,适用于小型的嵌入式领域。Concierge的使用需要遵守BSD形式的 license,网址为http://concierge.sourceforge.net

尽管基础框架中包含了很多功能和特性,但是真正的实现很小,并且运行在最小的JVM实现中。Concierge只占用80K的磁盘空间。基本的R4.x规范的实现需要占到300K~600K的磁盘空间。作为OSGi规范实现的Equinox,在其基础JAR包中包含了相当多的扩展功能,如优化的灵活性支持、高级签名管理、高可扩展性,即便这样,也仅占用了不足1M的磁盘空间。

2.11 总结

在简单性和一致性方面,OSGi框架规范本身是一个很优秀的例子。整套技术基于一系列简单而通用的概念,诸如模块化和服务等。OSGi起初定位于嵌入式领域,并由此开创了一些非常简化的处理方法,这些方法在如今的OSGi规范中均有所体现。

正是这种简单的特性让框架得以扩展,并且可以应用于很多不同的领域。OSGi的普适性是其核心价值。Eclipse采用了OSGi并随后将其广泛应用于富客户端,现在已经应用于服务器,由此为Java开发者和系统集成者提供了有力的支持。

查看评论