深入理解<mark>OSGI</mark>:第二章 模块层规范与原理(1)

 由  IcyFenix 发布

本章简介

从本章开始,我们将为读者讲解OSGi中最常用和最重要的内容。“最常用和最重要”意味着并不会涵盖OSGi规范的所有方面。“讲解”也不是对OSGi规范的直接翻译,笔者简化了OSGi规范中一些近乎于数学公式的严谨描述,改用尽可能通俗的语言进行介绍,并添加了目前业界在实际应用中使用规范中定义的内容的例子。

如果要学习Java语言,相信没有人会推荐从《Java语言规范》和《Java虚拟机规范》学起,因为刚开始接触Java的人没有必要把所有Java语言和Java虚拟机的细节都记下来。同样,对于之前没有接触过OSGi的读者,没有必要一字不漏地把本部分内容读完,这很枯燥。我们推荐初学者花上一、两个小时把本书第一部分的内容大致浏览一遍,然后迅速转入第三部分,配合第三部分的应用案例来学习会更有效率。如果读者此前已有基于OSGi的开发经验,或者已经阅读过本书第三部分,那么细读这部分OSGi规范内容将有助于了解OSGi的原理和完整的面貌。了解OSGi的规范和原理,是深入理解OSGi必不可少的过程。

2.1 OSGi规范概要

目前最新的OSGi规范是2012年7月发布的Release 5,Version5.0(后文简称为R5.0)版本,该规范定义了Java模块化系统所涉及的各种场景(开发、打包、部署、更新和交互等),以及其中用到的标准接口和参考模型。它是一份内容很全面、涉及范围很广泛的技术规范,从嵌入式系统到大型服务器系统,从模块的编码开发到部署使用,从OSGi核心框架到外围扩展服务都有专门的定义。

OSGi规范并不是单一的规范文档,而是由一系列子规范构成,这些子规范主要可分为两大部分,其中一部分用于描述OSGi的核心框架(OSGi Framework)。OSGi核心框架是一个可运行OSGi系统的最小集合,它由以下内容组成:

  • 执行环境(Execution Environment)。由于OSGi所适用的目标范围非常广泛,为了更好地处理不同硬件、软件环境对OSGi造成的兼容性问题,在建立其他约定之前,必须先定义好系统的执行环境。
  • 安全层(Security Layer)。描述了基于Java 2安全架构实现的代码验证、JAR文件数字签名、数字证书服务,安全层贯穿了OSGi框架的其他各个层次。
  • 模块层(Module Layer)。模块层从“静态”的角度描述了一个模块的元数据信息、执行环境定义、模块约束和解析过程、类加载顺序等内容。模块层是整个OSGi中最基础、最底层的层次。
  • 生命周期层(Life Cycle Layer)。生命周期层从“动态”的角度描述了一个模块从安装到被解析、启动、停止、更新、卸载的过程,以及在这些过程中的事件监听和上下文支持环境。
  • 服务层(Service Layer)。描述了如何定义、注册、导出、查找、监听和使用OSGi中的服务。服务层是所有OSGi标准服务的基础。
  • 框架API(Framework API)。由一系列通过Java语言实现的接口和常量类构成,为上面各层提供面向Java语言的编程接口。

构成OSGi规范的另外一部分内容是OSGi标准服务,这些标准服务试图以OSGi为基础,在软件开发的各种场景中(如配置管理、设备访问、处理网络请求等),建立一套标准服务和编程接口。软件开发所遇到的场景是多种多样、极其复杂的,因此OSGi对应定义的标准服务也非常庞大和复杂,OSGi所包含的数十个子规范大部分都用于定义这些标准服务。以下列举了一小部分较为常用的OSGi标准服务。

  • 事件服务(Event Admin Service)
  • 包管理服务(Package Admin Service)
  • 日志服务(Log Service)
  • 配置管理服务(Configuration Admin Service)
  • HTTP服务(HTTP Service)
  • 用户管理服务(User Admin Service)
  • 设备访问服务(Device Access Service)
  • IO连接器服务(IO Connector Service)
  • 声明式服务(Declarative Services)
  • 其他OSGi标准服务

大部分OSGi标准服务都没有写入OSGi核心(Core)规范之中,而是定义在OSGi服务纲要(Service Compendium)规范和企业级(Service Enterprise)规范之中。从上面对OSGi规范简要介绍我们可以总结出来,平时所说的“OSGi”大致包含了如图2-1所示的内容。

http://assets.osgi.com.cn/article/7289374/图2-1.jpg

图2-1 OSGi内容总览

虽然伴随着OSGi规范文档还发布了一些代码性质的内容,例如XML Schema定义和少量的JAR包,但是这些JAR包仅仅包含OSGi框架API及一些标准服务的接口。换句话说,仅仅靠这些随规范发布的代码是无法建立一个可运行的OSGi系统的。要让OSGi运行起来,还需要具体实现OSGi规范的程序才行,我们把这些程序称为“实现框架”或“OSGi实现”,如Eclipse的Equinox、Apache的Felix和Makewave的Knopflerfish等都是常见的OSGi实现。

2.2 Bundle

从本节开始,一直到本书结束,我们都会不断地提起“Bundle”这个词。Bundle是OSGi中最基本的单位,通俗地讲,如果说OSGi是基于Java平台的“模块化开发体系”,那么Bundle便是其中的“模块”。

OSGi中的Bundle是在JAR文件格式规范基础上扩展而来的,一个符合OSGi规范的Bundle首先必须是一个符合JAR文件格式规范的JAR包。与JAR文件格式兼容这点虽然没有太多技术含量可言,但是这个简单的举措极大地加速了OSGi的发展传播,它令OSGi的Bundle可以不经任何修改就直接应用于非OSGi的系统之中,也为将非OSGi的JAR包转换为可在OSGi系统运行的Bundle提供了很大的便利。

Bundle相对普通的JAR文件主要进行了以下三个方面扩展。

  • JAR文件格式规范里定义的/META-INF/MANIFEST.MF文件用于描述JAR包的元数据信息,如JAR包的版本、数字签名信息等,Bundle在MANIFEST.MF文件中添加了大量扩展定义,如描述该Bundle可以提供哪些资源、依赖哪些其他Bundle、启动或卸载时要执行哪些动作等,这部分内容我们会在2.3节中详细介绍。
  • 加入了一个可选的/OSGI-OPT文件夹,可以在其中保存一些与Bundle运行无关的信息,比如Bundle源码、软件说明书等。Bundle的使用者可以从中获取一些额外的信息,也可以安全地删除该文件夹,以节约OSGi系统的存储空间。
  • Bundle中可以包含一些具备特殊含义的程序和资源,如使用Bundle-Activator定义的初始化类、定义在OSGI-INF/l10n目录中的本地化信息等。

Fragment Bundle是一种特殊的Bundle,它无法独立存在,必须依附于某个其他的普通Bundle来使用,可以将它视为“Bundle的插件”、“模块中的模块”。

Fragment Bundle经常用来提供某些可选的功能,譬如为某个实现具体功能的Bundle提供一个中文语言包。有这个语言包,实现功能的Bundle能显示中文界面;在没有这个中文语言包时,实现功能的Bundle也能够正常使用。Fragment Bundle的另一项主要用途是隔离Bundle中经常变动的部分,譬如把系统的内部配置文件(开发模式还是生产模式、连接的数据库地址、调试级别等)集中在Fragment Bundle中,通过更换不同的Fragment Bundle来实现配置快速切换。

从静态角度(开发期)来看,Fragment Bundle与普通Bundle没有太大区别,它们都以JAR文件格式为基础,具备相同的元数据信息标记,标记的含义与设置方式也一样。区别仅仅是Fragment Bundle的元数据中会使用Fragment-Host标记说明它的宿主Bundle。

从动态角度(运行期)来看,Fragment Bundle与普通Bundle在运行时的处理差别却非常大,最重要的一点差异是Fragment Bundle不具备自己独立的类加载器。OSGi利用每个Bundle独立的类加载器互相协作来维护Bundle间导入、导出的依赖关系。没有类加载器,就无法直接与其他Bundle交互,必须依附于宿主,使用宿主Bundle的类加载器完成。关于这部分内容,我们在后面会有更详尽的介绍。

2.3 描述元数据

Bundle的元数据信息定义在/META-INF/MANIFEST.MF文件之中,OSGi规范中明确要求实现框架必须能够正确识别那些被预定义过的标记(在R5.0规范中预定义了28项标记),对于不可识别的标记以及不符合MANIFEST.MF标记格式的内容都要忽略且不能影响Bundle的正常解析。

2.3.1 预定义标记

以下列出了MANIFEST.MF文件中常用的预定义标记,除非特别说明,所列举的标记项都是可选的。对于其中某些较重要的标记(如Import-Package和Export-Package等),我们将在后续章节着重分析讲解。

(1)Bundle-ActivationPolicy

标记Bundle-ActivationPolicy设置Bundle的加载策略,该参数目前只有一个值:lazy,设置该参数后,Bundle将延迟激活,延迟至有其他的Bundle请求加载该Bundle中的类或资源时它才会被激活,如果不设置这个参数,那么Bundle启动时就会被激活。 示例: Bundle-ActivationPolicy: lazy

(2)Bundle-Activator

标记Bundle-Activator指明一个Activator 类,在Bundle启动和停止时会分别调用该类的start()和stop()方法,以便执行程序员所希望的动作,该类必须实现org.osgi.framework.BundleActivator接口。 Activator类通常用于在Bundle启动时注册和初始化服务,在Bundle卸载时注销这些服务。它很常用,但并不是必须的。 示例: Bundle-Activator: com.acme.fw.Activator

(3)Bundle-Category

标记Bundle-Category指明该Bundle的功能类别,可使用逗号分隔多个类别名称。这个功能类别仅供人工分类和阅读,OSGi框架并不会使用它。 示例: Bundle-Category: osgi, test, nursery

(4)Bundle-Classpath

标记Bundle-Classpath指明该Bundle所引用的类路径,该路径应为Bundle包内部的一个合法路径,如果有多个Classpath,使用逗号分隔。在介绍Bundle类加载过程时我们会详细介绍这个标记。 示例: Bundle-Classpath: /jar/http.jar,.

(5)Bundle-ContactAddress

标记Bundle-ContactAddress描述Bundle发行者的联系信息,仅供人工阅读,OSGi框架并不会使用它。 示例: Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563

(6)Bundle-Copyright

标记Bundle-Copyright描述Bundle的版权信息,仅供人工阅读,OSGi框架并不会使用它。 示例: Bundle-Copyright: OSGi (c) 2002

(7)Bundle-Description

标记Bundle-Description给出关于该Bundle的简短描述信息,仅供人工阅读,OSGi框架并不会使用它。 示例: Bundle-Description: Network Firewall

(8)Bundle-DocURL

标记Bundle-DocURL给出该Bundle文档的链接地址,仅供人工阅读,OSGi框架并不会使用它。 示例: Bundle-DocURL: http:/www.acme.com/Firewall/doc

(9)Bundle-Icon

标记Bundle-Icon给出该Bundle的显示图标,图标应为一张正方形的图片,并通过参数size指出图标的宽度。OSGi规范要求实现框架至少要支持PNG图片格式。 示例: Bundle-Icon: /icons/acme-logo.png;size=64

(10)Bundle-License

标记Bundle-License给出该Bundle的授权协议信息。 示例: Bundle-License: http://www.opensource.org/licenses/jabberpl.php

(11)Bundle-Localization

标记Bundle-Localization给出该Bundle在不同语言系统下的本地化信息,如果不设置此标记,它的默认值为OSGI-INF/l10n/bundle。 示例: Bundle-Localization: OSGI-INF/l10n/bundle

(12)Bundle-Mani