OpenDoc Series':OSGI实战(七):1

 由  ValRay 发布

七. 深入 OSGI

经过对 OSGI 框架使用的学习,相信大家对于 OSGI 中的概念有了或多或少的理解,同时也会在使用的过程中产生各种各样的疑问,在这个章节中将深入学习 OSGI 框架背后的思想,学习 OSGI 规范中是怎么定义 Bundle 的元数据的、怎么来管理 Bundle 的、怎么来管理 Service 的等等,以在实践中更好的使用 OSGI 框架搭建系统,同时通过对于 OSGI 的学习,不断的改善和提升之前在实战章节中的用户登录验证模块,以使其更加的贴合实际的应用。

7.1. 关于 OSGI

OSGI联盟[ 官方网站:www.osgi.org ]成立于 1999 年 3 月,致力于制定管理本地网络设备服务的规范。OSGI组织是为家用设备、汽车、手机、桌面、小型办公环境以及其他环境制定下一代网络服务标准的领导者。

OSGI Service Platform 规范提供了开放和通用的架构,使得服务提供商、开发人员、软件提供商、网关操作者和设备提供商以统一的方式开发、部署和管理服务。OSGI 通过提供灵活的服务部署机制和强大的管理功能增强了设备的智能性。OSGI 规范制定 的目标是为机顶盒、服务网关、Cable Modems、PC、汽车、手机等等提供服务。

7.2. OSGI R4 规范

OSGI R4 规范由 Framework、Standard Services、Framework Services、System Services、 Protocol Services、Miscellaneous Services 共同组成,在规范中对以上的各个方面进行 了详细的介绍和规定,在这个章节中会深入的介绍 OSGI R4 中的关键部分,同时结合理论对之前的用户登录验证模块的实现进行讲解和改善。

7.2.1. Core Framework

Core Framework 是 OSGI 规范中的核心部分,它为基于 OSGI 的应用系统提供了标准的运行环境,从本质上保证了基于 OSGI 规范化的开发和部署动态性的系统。 OSGI 框架由 4 层组成:

  • L0:运行环境
  • L1:模块
  • L2:生命周期管理
  • L3:服务注册
可参见右边这张 OSGI Framework 的经典图:

L0 运行环境是指标准的 JAVA 环境。只要具备Java2的构造和轮廓的都是被认可的运行环境。OSGI同时也定义了一个可运行Bundles的小环境的标准。

L1模块层定义了所采用的类加载(Classloader)机制。OSGI 是一个强大、严格、规范的类加载模型,基于Java但增加了模块化。在Java中,通常都是由一个Classloader来加载所有的类和资源文件。在OSGI模块层中则为模块提供各自的classloader,同时为模块的关联提供控制。

L2生命周期管理层则为Bundles的动态安装、启动、停止、更新和卸载提供了支持。基于 L1 提供的模块类加载机制的基础上,增加了一个对于Bundle的管理的API。

L3增加了服务注册。服务注册为Bundles提供了一个动态的协作模型。本来Bundles可通过传统的class共享方式来实现协作,但在动态的安装和卸载代码的环境下这种方法是不适用的。服务注册为 Bundles间共享Objects提供了一种可用的模型,OSGI 提供了一堆的事件来通知服务的使用者关于服务的注册和卸载,服务其实就是简单的Java objects。很多服务象objects一样,例如http server,而有些服务则代表了现实世界中的对象,例如蓝牙手机。在 OSGI Framework 中还包括一个安全层次(Security Layer),OSGI 的安全层次基于Java的安全机制进行了扩展,增加了一些新的约束以及填补了java安全机制中的遗漏。

OSGI Framework 作为 OSGI 规范的核心,其中的定义决定了基于 OSGI 框架如何去设计、开发以及部署系统,下面就按照 OSGI Core Framework 的分层规则来分别讲解各个层次。

7.2.1.1. Module Layer

Module Layer定义了在OSGI框架中是怎么去按照Module的思想去开发的,由于在目前的Java标准中并没有明确的按照Module方式定义的开发规范[ 目前正在制定的JSR277 就是Java Module System的规范了 ],但按照Module 的思想进行开发系统是很流行的思想,这也就使得象Jboss、Netbeans都有一套自己的Module规范,在这样的一套规范中,需要定义的主要是Module是如何去组织的、如何去部署的以及如何去共享Module的package的。

  • Module 的定义

在 OSGI 规范中,将 Module 命名为 Bundle,所以,在 OSGI 框架中是采用 Bundle 的方式来组织和部署系统的,Bundle 的概念在之前的章节中已经介绍过了,经过实战的演练,也已经知道 Bundle 和普通的 Java 工程唯一不同的是需要在 MANIFEST.MF 中编写 Bundle 的元数据信息,来看看在 MANIFEST.MF 中可以定义哪些 Bundle 的元数据信息:

属性 属性描述
Bundle-Activator Bundle 的 Activator 类名。 示例:
Bundle-Activator:org.riawork.demo.Activator
Bundle-Category Bundle 的分类属性描述。 示例:
Bundle-Category:Opendoc,OSGI
Bundle-Classpath Bundle 的 Classpath。示例:
Bundle-Classpath:/bin,/lib/log4j.jar
Bundle-ContactAddress 提供 Bundle 的开发商的联系地址。示例:
Bundle-ContactAddress:ShangHai
Bundle-Copyright Bundle 的版权。
Bundle-Description Bundle 的描述信息.
Bundle-DocURL Bundle的文档URL地址。
Bundle-Localization Bundle 的国际化文件。示例:
Bundle-Localization: OSGI-INF/l10n/bundle
Bundle-ManifestVersion 定义 Bundle 所遵循的规范的版本,OSGI R3 对应的值为 1,OSGI R4 对应的值为2.
Bundle-Name Bundle 的有意义的名称。
Bundle-NativeCode Bundle 所引用的 NativeCode 的地址。
Bundle-RequiredExecutionEnvironment Bundle 运行所需要的环境,如可指定为需要 OSGI R3、Java 1.4、Java 1.3 等。
Bundle-SymbolicName Bundle 的唯一标识名,可采用类似java package名的机制来保证唯一性。
Bundle-UpdateLocation Bundle 更新时连接的 URL 地址。
Bundle-Vendor Bundle 的开发商。
Bundle-Version Bundle 的版本。
DynamicImport-Package Bundle 动态引用的 package.
Export-Package Bundle 对外暴露的 package。
Fragment-Host Fragment 类型 Bundle 所属的 Bundle 名。
Import-Package Bundle 引用的 package。
Require-Bundle Bundle 所需要引用的其他的 Bundle。

对于之上的 Bundle 的元数据属性的值,都支持增加附加过滤属性的方式,如:Import-Package 可以是这样的格式:

Import-Package: org.riawork.opendoc.osgi;version=”[1.0,2.0)”;resolution:=mandatory,org.riawork.op endoc.riawork;Company=RIAWork
  • Module 的 package 共享机制

在开发Bundle章节中已经学习使用Export-Package和Import-Package来实现 Bundle的包的提供和引用,但其实Module Layer还定义了更为实用和严格的 package共享机制。

过滤引用需要的 package

在开发Bundle章节中我们采用的是直接通过import-package方式来引用所需要的package的,未做任何的过滤设置,其实可以通过在import-package 中设置其他的过滤属性,以更加准确的获取所需要引用的package,可以通过版本过滤、Bundle元数据信息过滤、自定义属性过滤、必须的属性过滤来实现过滤获取所需的package。

版本过滤

可在 Import-Package 指定需要导入的 package 的版本范围或版本,示例:

Import-Package: org.riawork.opendoc.osgi;version=”1.0”

说明导入时只导入版本为 1.0 的 package,那么相应的在导出的 package 中可以指定其版本:

Export-Package: org.riawork.opendoc.osgi;version=”1.0”

版本过滤时还可以采用一种版本范围过滤的方式,即采用在数学中经常使用的区间的方式,如 [1.0,2.0] ,说明可引用版本为1.0=

Import-Package: org.riawork.opendoc.osgi;version=”[1.0,2.0]”

而[1.0,2.0)则表明引用的版本需要 1.0= 版本过滤的作用在于能够动态的更新系统的版本或者更好的去控制系统的版本兼容性问题等。

Bundle 元数据信息过滤

在引用 package 时还可使用 Bundle 元数据信息来进行过滤,同样通过 在 import-package 增加附加属性来实现,例:

Import-Package: org.riawork.opendoc.osgi;bundle-symbolic-name=B

自定义属性过滤

由于 Bundle 中的元数据基本都是可自定义属性的,所以在import-package 时也可以采用自定义属性过滤的方法,例:

Import-Package:org.riawork.opendoc.osgi;company=RIAWork

那么它所引用的就是这个 Bundle export 的 package:

Export-Package:org.riawork.opendoc.osgi;company=”RIAWork”
必须的属性过滤
之上的自定义属性过滤是一种非强制性的,而必须的属性过滤是一种 强制性的,在 Export-Package 中通过 mandatory 来指定必须匹配的属性,例: Export-Package:org.riawork.opendoc.osgi;company=”RIAWork”;security=false;man datory:=security Import-Package:org.riawork.opendoc.osgi;company=RIAWork 在这样的情况下是引用不到上面 Export 的 package ,因为在 Export-Package 中指定了必须匹配 security 的属性。
Package 约束

Package 约束是在使用 import-package 时要较为注意的一个地方,举例来说: A Bundle:
Import-package: org.riawork.opendoc.osgi;version=”2.0”
Export-package: org.riawork.opendoc.riawork
B Bundle:

Export-package: org.riawork.opendoc.osgi; version=1.0

C Bundle:

Export-package: org.riawork.opendoc.osgi; version=2.0

这个时候如果 D Bundle 按照下面这样的方式引用包则会导致错误: D Bundle:
Import-package: org.riawork.opendoc.osgi,org.riawork.opendoc.riawork;version=”1.0” 这个时候出错的原因就在于 D Bundle 去引用了版本均为 1.0 的 org.riawork.opendoc.osgi 和 org.riawork.opendoc.riawork,但唯一的暴露 org.riawork.opendoc.riawork 的 A Bundle 引用的却是版本为 2.0 的 org.riawork.opendoc.osgi,这个时候就出现冲突的问题了。 在 import 其他 bundle 提供的 package 时要注意这个问题,在 package 引用时如有和其他 bundle 提供的 package 同样的引用时,要避免出现版本上的冲突,其实上面的 D Bundle 只要改成这样就可以了:
Import-package:org.riawork.opendoc.osgi;version=”2.0”,org.riawork.opendoc.riawo rk

限定导出的 package 中的类

在 OSGI 中还支持限定导出的 package 中的类,例如在同一个 package 下同时有接口类和实现类,在对外暴露 package 时只希望接口类被暴露出,则可使用这个来限定:
Export-package: org.riawork.opendoc.osgi;exclude:=”Impl”;include=”Val” 在这样的设置下,只有 org.riawork.opendoc.osgi 下以 Val 开头且不以 Impl 结尾的类被暴露出来,也就是说在别的 Bundle 即使引用了 org.riawork.opendoc.osgi package,也只能使用其中 Validator 这样的类,但不能使用 ValidatorImpl 这样的类。

  动态的获取引用的 package
动态的获取引用的package和直接引用package的策略不同的地方在于:如果采用直接引用package的策略(也就是import-package),那么OSGI框架在 resolve Bundle时就会对其import的package做检测,如不可用的话就会导致 resolve失败;但如果采用动态获取引用package的策略,则直到使用这个 package时才会去获取,而不是在resolve阶段获取[ 在Lifecycle Layer章节中会详细介绍resolve ]。   在 OSGI 框架有这么两种方法来实现动态的获取引用的 package:
Dynamic Imports
这 种 方 式 为 不 使 用 import-package , 而 是 改 为 使 用DynamicImport-Package的来方式来引用其他Bundle export的package。 如 Import-package: org.riawork.opendoc.osgi 改为 DynamicImport-Package:org.riawork.opendoc.osgi
Resolution Directive
这种方式为继续使用 import-package,只是增加附加属性来声明引用的package 采用 dynamic 方式载入,例: Import-package: org.riawork.opendoc.osgi;resolution:=optional - **Module 的 Classloader 机制** Module 机制中重要的一点就是拥有独立分离的 classloader 机制而得以保证 Module 的闭合,那么来看看在 OSGI 规范中是如何管理 Bundle 的 classloader 的。 OSGI 框架的 classloader 由 System Classloader、Bundle Classloader 共同组成, 每个 Bundle 拥有独立的 Classloader。 在 OSGI 规范中有张经典的图可说明 Bundle 的 class 的加载机制的:
![](http://assets.osgi.com.cn/article/7289449/26.jpg)
图表 7 OSGI Class loading流程图
如图所示,OSGI 框架在加载 Bundle 中的类时按照这样的步骤进行:

如需要加载的为 java.*的类,则直接委派给 Parent Classloader,如在 parent Classloader 中找到了相应的类,则直接返回,如未找到,则抛出NoClassDefFoundException。

如加载的不是 java.*的类,则进入这一步。判断加载的类是否属于 boot delegation 中配置的范围,如不属于则进入下一步,如属于则继续委派给 Parent Classloader,如在 Parent Classloader 中找到则直接返回,如未找到,则进入下一步。可在配置文件中编写 org.osgi.framework.bootdelegation 的属性来决定 boot delegation 的范围,示例:

org.osgi.framework.bootdelegation=sun.*,com.sun.*

如属于 Bundle Import package 中的类,则交给 export package 的 Bundle 的classloader 进 行 加 载 , 如 加 载 失 败 , 则 直 接 抛 出 NoClassDefFoundException,如加载成功则直接返回。这步就解释了之前在注意事项中所写的需要注意的包的问题。

如不属于 Bundle Import package 中的类,则搜索是否属于 Require Bundles 中 export 的 package 的类,如属于则交由 export package 的 Bundle 的 Classloader 进行加载,如加载成功则直接返回,如加载失败则进入下一步。

在 Bundle classpath(就是在 Bundle-Classpath 所配置的路径)中搜索需要加载的类,如加载成功,则直接返回,如加载失败则继续进入下一步。

搜索 Fragment Bundle(还记得配置的 Fragment-Host 吧)的 classpath,如加载成功,则直接返回,如加载失败则继续进入下一步。

判 断 是 否 属 于 export 的 package , 如 属 于 则 直 接 抛 出NoClassDefFoundException,如不属于则进入下一步.

判断是否属于 DynamicImport 的 package ,如不属于则直接抛出 NoClassDefFoundException,如属于则使用 export package 的 Bundle 的 ClassLoader 进行加载,如加载成功则直接返回,如加载失败则抛出 NoClassDefFoundException。

对于 Bundle 中的资源文件,可使用 bundle.getResource、bundle.getEntry 或 bundle.findEntries 来获取,返回的为一个可被转变为 java.net.URL 的对象,通过 URL 就可加载到相应的资源文件,如果要获取到其他 Bundle 的资源文件则 需通过设置 Require-Bundle 的方式才可获取,Require-Bundle 也可视为实现资源文件共享的一种方式,不过 Require-Bundle 并不是被推崇的一种方式,在 OSGI 规范中,认为 Require-Bundle 会造成 split packages。

  • Module 的国际化

标准的Java国际化的方案,默认国际化文件的目录为:OSGI-INF/l10n,配置 文件名为类似 bundle语言国家变量.properties,如:bundleen.propties,同样,可以在 Bundle 的元数据中通过 Bundle-Localization 来指定国际化文件所在的目录,在使用国际化文件的情况下,在 MANIFEST.MF 文件中可采用%属性名的方式来使用国际化文件中的配置:

Bundle-Name: %BundleName  
Bundle-Version:%Bundle Version

bundle_en.properties 文件中配置如下:

BundleName=OSGI Opendoc
Bundle Version=1.0
- Module 的校验

如果 Bundle-ManifestVersion 没有设置,则会使用其默认值 1,此时对应的为遵循 OSGI R3 规范,OSGI R4 规范中定义的新的 Bundle 的元数据信息则被忽略。

这里列出了一些会导致 Bundle 安装失败的原因:

Bundle-RequiredExecutionEnvironment 中的值和可用的执行环境不符;

缺少 Bundle-SymbolicName;

重复的导入同一个 package;

导出或导入 java.*;

导出的 package 中必须的属性未定义;

安装一个已经安装了的同版本、同样标识名的 Bundle;

更新一个已经安装了的同版本、同样标识名的 Bundle;

同时使用了 specification-version 和 version;

Bundle-ManifestVersion 的值不是 1 或 2,除非将来推出的新的 OSGI 规范接受新的值。
- 三种特殊形式的 Bundle

Require Bundles
Require Bundle 其实不能算什么特殊形式的 Bundle,它只是可以直接被其 他 Bundle 通过 Require-Bundle 来使用的 Bundle。 如果使用了 Require-Bundle,那么就可以使用该 Bundle 中所有的资源文件 和 export 的 package。

Fragment Bundles
Fragment Bundle 是一种比较特殊的 Bundle,它本身并不拥有独立的 classloader,可以把它看成是 Bundle 的一种附属,它通过在元数据中指定 Fragment-Host 来说明其所依附的 Bundle,只有在该 Bundle 使用时才会激 活到这个 Fragment Bundle。

Extension Bundles
Extension Bundle 也是一种比较特殊的 Bundle,它用于扩展 system bundle,通过 Fragment-Host 指定到 system bundle 的方式来实现对 system bundle 的扩展。

经过上面对于 Module Layer 规范的解读,可以看出 OSGI 的 Module Layer 定义了完整的 Module 开发、部署、共享 package 等的规范,而基于 Module Layer 一定程度上实现了系统的动态性,使得设计人员在基于 OSGI 框架时可放心的按照 Module 的方式进行设计,使得开发人员在基于 OSGI 框架时可按照规范的 Module 开发方式来进展。

7.2.1.2. Lifecycle Layer

Lifecycle Layer 基于 Module Layer,使得基于 OSGI 框架可以动态的对 Bundle 的生 命周期进行管理。

  • Bundle 的状态

    Bundle 的状态分为六种
    

    INSTALLED
    Bundle 已经成功的安装了。

    RESOLVED
    Bundle 中所需要的类都已经可用了,RESOLVED 状态表明 Bundle 已经准备好了用于启动或者说 Bundle 已被停止。

    STARTING
    Bundle 正在启动中,BundleActivator 的 start 方法已经被调用,不过还没返回。

    ACTIVE
    Bundle 已启动,并在运行中。

    STOPPING
    Bundle 正在停止中,BundleActivator 的 stop 方法已经被调用,不过还没返回。

    UNINSTALLED
    Bundle 已经被卸载了。
  • 管理 Bundle 的状态

先来形象的看看基于 OSGI 框架是如何管理 Bundle 的状态的: 启动之前我们实现的用户登录验证模块,输入 ss 可以查看此时系统中 Bundle 的状态:

此时系统中所有的 Bundle 都处于 ACTIVE 状态,输入 stop 8 回车后再看看 id 为 8 的 Bundle 的状态:

再输入 uninstall 2,回车:

再输入 start 8,回车:

下面这张图说明了 Bundle 的状态是怎么转换的:

来看看基于 Lifecycle Layer 是怎么完成这些状态的转变的:

安装 Bundle
通过 BundleContext 的 installBundle 方法来安装 Bundle,在安装前首先需要对 Bundle 进行校验,导致 Bundle 安装失败的原因在之前 Module Layer 章节中已经描述过了,如校验通过,OSGI 框架中将安装 Bundle 到系统中,此时 OSGI 框架会分配一个高于现在系统中所有的 Bundle 的 ID 给新的 Bundle,安装完毕后 Bundle 的状态就变为 INSTALLED 了,同时会返回 bundle 对象,在 Bundle 安装后就要使用 bundle 对象来管理 Bundle 的生命周期状态了。

解析 Bundle
Bundle 安装完毕后,OSGI 框架将对 Bundle 进行解析,以检测 Bundle 中的类依赖等是否正确,如有错误则仍然处于 INSTALLED 状态,如成功 Bundle 的状态则转变为 RESOLVED。

启动 Bundle
在启动 Bundle 前需检测 Bundle 的状态,如 Bundle 状态不为 RESOLVED,那么需要先解析 Bundle,如启动一个解析失败的 Bundle,则会抛出 BundleException,但此时 Bundle 的状态仍然会被设置为 ACTIVE;如 Bundle 的状态已经是 ACTIVE,那么启动 Bundle 对它不会产生任何影响。 通过 BundleContext 的 getBundle 方法可获取指定 Bundle ID 的 Bundle 对象,在获取到Bundle对象后可使用Bundle对象的start方法来启动Bundle,此时会调用 MANIFEST.MF 中的 Bundle-Activator 属性对应的 BundleActivator 类的 start 方法(如存在 BundleActivator 类),在 start 方法 执行的过程中 Bundle 的状态为 STARTING,当 start 方法执行完毕后 Bundle 的状态转变为 ACTIVE,如 start 方法执行失败,Bundle 的状态转变为 RESOLVED。 BundleActivator 类是可以不需要的,建议不要在 BundleActivator 中初始 化过多的东西,这样会使得系统的启动速度会变得很慢,同时也会消耗大量的内存,而且 OSGI 对于动态性的良好支持使得尽可以在需要的时候才去获取所需的资源。

停止 Bundle
通过 BundleContext 的 getBundle 方法可获取指定 Bundle ID 的 Bundle 对象,在获取到Bundle对象后可使用Bundle对象的stop 方法来启动Bundle,此时会调用 MANIFEST.MF 中的 Bundle-Activator 属性对应的 BundleActivator 类的 stop 方法,在 stop 方法执行的过程中 Bundle 的状态为 STOPPING,当 stop 方法执行完毕后 Bundle 的状态转变为 RESOLVED,如 stop 方法执行失败,Bundle 的状态则继续保留原状态。 即使 Bundle 已经停止,其 export 的 package 仍然是可以使用的,这也就 意味着可以执行 RESOLVED 状态的 Bundle 中 export package 的类。

卸载 Bundle
通过调用 Bundle 对象的 uninstall 方法可完成 Bundle 的卸载,此时 Bundle 的状态转变为 UNINSTALLED。

即使 Bundle 已卸载,其 export 的 package 对于已经在使用的 Bundle 而言仍然是可用的,但对于新增的 Bundle 则不可使用已卸载的 Bundle export 的 package。

  在管理 Bundle 的状态时,OSGI 主要是通过 Bundle、BundleContext 这两个对象来实现,Bundle 对象中除了对于 Bundle 的生命周期管理的方法之外,还提 供了象 getHeaders、loadClass、getResource 这些方法,getHeaders 方法可用于 获取 MANIFEST.MF 中的属性值,loadClass 可用于加载 bundle 中的类, getResource 可用于获取 Bundle 中的资源文件。

  • 监听 Bundle 的状态

在监听 Bundle 的状态上 OSGI 采用的是典型的 Java 中的事件机制,在 OSGI 中事件分为 Framework Event 和 Bundle Event 两种,Framework Event 用于报告 Framework 已启动、改变了 StartLevel、刷新了 packages 或是出现了错误;而 Bundle Event 则用于报告 Bundle 的生命周期的改变。

7.2.1.3. Service Layer

Service Layer 定义了 Bundle 动态协作的服务发布、查找和绑定模型,Service Layer 基于 Module Layer 和 Lifecycle Layer,使得 OSGI 形成了完整的动态模型。

不过 Service Layer 的定义比较简单,是一个典型的 Service Locator 模式的模型,Service 通过 BundleContext 完成注册和获取。

  • 服务的注册

可在任何时候通过 BundleContext 的 registerService 方法来完成服务的注册,和其他的服务框架一样,在 OSGI 中注册服务时也可以注册一个 ServiceFactory 的类,服务成功注册后会返回 ServiceRegistration 对象,通过这个对象的 unregister 方法可卸载服务。

  • 服务的获取

在获取服务时除了按照实战章节中的示例方式获取之外,还可通过构造 Dictionary 对象来增加过滤属性,以更加准确的获取所需要的服务。 同样的,可在任何时刻通过 BundleContext 来获取服务,而通过 BundleContext 在需要的时候获取服务则可保证获取服务的动态性。

  • 服务的监听

      服务的监听在实战章节中已经举例说明了,通过实现 ServiceListener 可监听 Service 的状态,通过 BundleContext 注册监听器,在注册监听器时可增加过滤 的属性,以更加准确的监听希望监听的服务的事件。

在 OSGI R4 推出 Declarative Services 之后,Service Layer 其实就已经成为鸡肋了, Declarative Services 提供了更好的服务注册、获取、监听等方式,使得其成为了 OSGI R4 中的重要角色,并由此替代了 Service Layer。

7.2.1.4. Security Layer

Security Layer 在 OSGI 中采用的主要是 java 本身的 security 策略和数字签名策略,这方面就不在这里做过多介绍,如需要了解的话请翻阅《OSGI Service Platform Release 4》。

查看评论