OSGi R4服务平台核心规范 :第三章 模块层(1)

 由  满江红开放技术研究组织 发布

版权说明

本文档版权归原作译者所有。 在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。

如需部分或者全文引用,请事先征求作译者意见。

如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给作者。当然,倘若有时间和能力,能为技术群体无偿贡献自己的所学为最好的回馈。

本文档可从http://www.redsaga.com获取最新更新信息

3.1 介绍

Java平台只提供了对打包、部署和对Java应用和组件检验的最小支持。因此,很多基于java的项目,如JBoss、NetBeans,常常借助于专用的类加载器来创建用户模块层,以实现打包、部署和对Java应用和组件检验。OSGi框架提供了对java模型化的一般和标准的解决方案。

3.2 Bundle

框架定义了模型化单元,称之为一个bundle。一个bundle由java的类和其他资源组成,可以为终端用户提供功能。通过良好定义的方式,Bundle可以和导入(importer)及导出(exporter) Bundle之间共享Java包。

在OSGi服务框架中,bundle是仅有的需要部署的Java应用实体。

Bundle以JAR文件的方式进行部署。JAR文件使用ZIP的格式存储应用程序以及所需的资源。在文献[27]中对Zip文件格式进行了描述。

一个bundle是一个如下的JAR文件:

  • 拥有提供服务所必须的资源。这些资源可以是java的class文件,或者是其他的数据如HTML文件,帮助文件,图标文件等。一个bundle JAR文件也可以嵌入其他JAR文件作为资源,但是不支持多层嵌套的JAR。

  • 有一个manifest文件描述JAR文件内容和bundle的信息。该文件处于JAR的头部,提供框架需要的安装和激活bundle所需的信息。例如,它对其他资源如JAR文件的依赖这种状态信息必须在bundle运行之前加载。

  • 可以在OSGI- OPT文件夹提供可选的文档信息,该文件夹可以位于JAR文件根目录或者它的子文件夹中。OSGI- OPT文件夹中的内容都是可选的。例如,可以在其中保存bundle的源代码。管理系统可以删除该文件夹内容,以便于节约OSGi服务平台的存储空间。

当一个bundle开始运行,通过OSGi服务平台,它开始对安装在平台内的其他bundle提供功能和服务。

3.2.1 Bundle Manifest Headers

Bundle的描述信息在一个manifest文件中,在JAR文件中的META-INF目录下的MANIFEST.MF文件。框架在manifest文件头中定义了Export- Package和Bundle- Classpath这样的OSGi manifest 头,bundle的开发人员可以使用它们提供bundle的描述信息。Manifest头部必须严格遵照文献[28]中的“manifest format”规则进行定义。

框架的实现必须:

  • 处理manifest的主要部分。manifest中的单独部分只用于对bundle的签名验证。
  • 忽略不可识别的manifest标记信息。因此,bundle开发人员可以在manifest文件中定义附加的其他信息。
  • 忽略不可识别的指令和属性。

下面列出了对manifest标记的所有规定,除了特别指明,所有的标记信息都是可选的。

3.2.1.1 Bundle- Activator: com.acme.fw.Activator

描述指出启动和停止bundle的类名称。

3.2.1.2 Bundle- Category: osgi, test, nursery

描述用逗号分隔的分类名称。

3.2.1.3 Bundle- Classpath: /jar/http.jar,.

定义用逗号分隔的路径,包含的内容有JAR文件和包含类和资源的目录(bundle内部)。 点号(‘.’)代表JAR文件的根目录,同时也是默认的。参考bundle类路径一节。

3.2.1.4 Bundle- ContactAddress: 2400 Oswego Road, Austin, TX 74563

标识bundle发行者的联系信息。

3.2.1.5 Bundle- Copyright: OSGi (c) 2002

描述bundle的版权信息。

3.2.1.6 Bundle- Description: Network Firewall

对bundle的简短描述信息。 3.2.1.7 Bundle- DocURL: http:/www.acme.com/Firewall/doc Bundle文档的链接地址。

3.2.1.8 Bundle- Localization: OSGI- INF/l10n/bundle

描述bundle的本地文件地址,默认值是OSGI- INF/l10n/bundle。

3.2.1.9 Bundle- ManifestVersion: 2

定义了bundle遵循本规范的那种规则。默认值为1,表示第三个版本的bundle,2表示第四个版本及其后发布的版本。也可以为OSGi新发布的版本定义更高的数字。

3.2.1.10 Bundle- Name: Firewall

定义了一个具有可读性的名字来标识bundle。应该是一个简短易读没有空格的名字。

3.2.1.11 Bundle- NativeCode: /lib/http.DLL; osname = QNX; osversion = 3.1

对bundle中包含的本地代码库的规范。可以参考加载本地代码库一节。

3.2.1.12 Bundle- RequiredExecutionEnvironment: CDC- 1.0/Foundation- 1.0

描述在服务平台上必须出现的了可执行环境的,用逗号分割。参考可执行环境一节。

3.2.1.13 Bundle- SymbolicName: com.acme.daffy

提供了bundle的一个全局的惟一的标志符。名称应该是基于反域名解析的。参考bundle标志符一节。这部分是必须的。

3.2.1.14 Bundle- UpdateLocation: http://www.acme.com/Firewall/bundle.jar

描述bundle的更新地址。如果bundle需要更新,则使用这个地址进行更新。

3.2.1.15 Bundle- Vendor: OSGi Alliance

描述bundle的发行者信息。

3.2.1.16 Bundle- Version: 1.1

描述bundle的版本信息。默认值为0.0.0

3.2.1.17 DynamicImport- Package: com.acme.plugin.*

包含了一个逗号分隔的动态导入包清单。参考动态导入包。

3.2.1.18 Export- Package: org.osgi.util.tracker;version=1.3

描述导出包声明。参考导出包。

3.2.1.19 Export- Service: org.osgi.service.log.LogService

不建议使用

3.2.1.20 Fragment- Host: org.eclipse.swt; bundle- version="[3.0.0,4.0.0)"

描述本片断中的主bundle,参考片断主体(Fragment- Host)一节。

3.2.1.21 Import- Package: org.osgi.util.tracker,org.osgi.service.io;version=1.4

声明bundle导入的包。参考导入包一节。

3.2.1.22 Import- Service: org.osgi.service.log.LogService

不建议使用

3.2.1.23 Require- Bundle: com.acme.chess

指定bundle中需要其他bundle导出的内容。参考bundle需求一节。

3.2.2 标记语构

每一个manifest标记都有自己的语法结构,语法结构在文献[29]W3C EBNF中进行了描述。接下来的部分定义了一些常用的规则。

3.2.3 通用标记语构

很多manifest标记都有一个通用的结构,一般如下所示:

http://assets.osgi.com.cn/article/7289393/0.png

参数可以是指令也可以是属性。一般指令在框架中用于潜在语义支持,属性用于匹配和比较。

3.2.4 版本

版本规格在很多地方都会用到,版本说明采用以下的结构:

http://assets.osgi.com.cn/article/7289393/1.png

版本标记不能有任何空格,默认值为0.0.0

3.2.5 版本范围

用数字间隔表示法描述版本的范围,参考数字间隔表示法惯例。

版本范围表示的结构如下:

http://assets.osgi.com.cn/article/7289393/2.png

如果一个版本范围指定是一个单独的版本,必须写成 [version,∞) 式样。默认的没有指定版本范围的值为0,等价于:[0.0.0,∞)

注意在版本范围定义中使用逗号分隔,双引号结束,如下所示:

http://assets.osgi.com.cn/article/7289393/3.png

下表中,对于左边列规定的范围,如果x满足右边列的谓词判定,则可以认为x在范围之内。

示例:

http://assets.osgi.com.cn/article/7289393/4.png

3.2.6. 过滤器正则表达式

OSGi规范广泛使用了过滤器表达式。过滤器表达式为约束提供了简洁的表达方法。

过滤器正则表达式是基于LDAP的查找过滤器字符串表达法,在文献[23]中有详细描述。需要注意的是RFC 2254,一种LDAP查找过滤器的字符串表示法,替代了RFC 1960,只是添加了扩展的匹配,但是OSGi框架的API不提供对它的支持。

一个LDAP查找过滤器使用前缀形式字符串表示,采用如下的语法定义

http://assets.osgi.com.cn/article/7289393/5.png

在属性中,左边字符串表示一个属性或者是键。属性名称是大小写不敏感的,也就是说cn和CN代表的是同样的属性。在属性名称中不能含有'='、'>'、'<'、'~'、'(' 或者 ')'字符,在属性名称中可以嵌入空格,但是开始和结尾的空格都被忽略的。右边表示值,也可以是通过和过滤属性的值进行比较而得到的部分值。

如果值中间含有字符'*'、’(’ 或者 ')'中的一个,那么必须要采用反斜杠进行转义处理。在属性值中,空格是由含义的,空格由Character.isWhiteSpace()定义。

虽然在子串和现在产品中都可以生成“attr=*”结构,但是这种结构只是用于表示一个现有过滤器。

对过滤器求值有具体的框架实现来完成,但是最少需要忽略大小写和空格。需要使用到探测法和其他一些智能逼近的代码。

过滤器求值之后,对具体的属性值和过滤器的值进行比较。对这些值的比较并不是直接进行的。字符的比较和数字比较不一样,也同样适用于多值属性值的比较。属性键值必须是字符串类型的,这样大小写不敏感的属性就可以获得属性值。

http://assets.osgi.com.cn/article/7289393/6.png

下面的规则用于进行比较:

  • 字符串—利用字符串进行比较
  • 整数、长整形数、浮点数、双精度数、字节、短整形数、字符对象和图元—利用数字进行比较。
  • 布尔型对象—利用等式比较。
  • 数组或者是向量—根据保存内容进行比较。有可能其中的内容具有多种类型,或者为空。如果其中保存的内容不是上述类型,而且这种类型有一个带String类型参数的构造方法,那么框架将通过把属性值传递给构造函数中的这个String参数,构造出一个这样的对象,然后再根据上述规则进行比较。
  • 可比较的对象—通过比较接口进行比较。
  • 其他对象—相等关系。

如果没有上述规则可以对应,那么比较的值为false。

如果一个属性具有多个值,那么过滤器只要满足其中一个就是匹配的。

例如:

http://assets.osgi.com.cn/article/7289393/7.png

那么dict可以满足这样的过滤器:(cn=a)和(cn=b)

3.3. 运行环境

在多个运行环境下执行的bundle必须在其manifest文件中标注这种对环境的约束。标记名称是Bundle- RequiredExecutionEnvironment,这个节点的结构是用逗号分割的多个运行环境的清单。

http://assets.osgi.com.cn/article/7289393/8.png

例如:

http://assets.osgi.com.cn/article/7289393/9.png

如果一个bundle有这样一个标记信息,那么bundle只能使用在提及的运行环境中签名了的方法。在bundle中,必须列出所有的可以运行的环境。

3.3.1. 运行环境命名

运行环境需要一个合适的名字,理由如下:

  • bundle在安装之前需要知道框架提供的运行环境。
  • 提供框架使用的运行环境信息。

运行环境的命名可以使用除了空格之外的其他任何字符,也可以使用逗号(’ ,’, or \u002C),OSGi联盟定义了一系列的运行环境。

以后,命名模式将采用J2ME格局形式,对于命名模式没有一个非常清晰的定义,但是在不同的规范中,名称都是类似的。

J2ME模式通过配置和设备简表(profile)来设计运行环境。OSGi将这两部分合并为一个名称,用来描述运行环境。 在J2ME已经存在一些定义好的运行环境,可以用于定义服务平台的服务器。运行环境的标题必须和现有的规范兼容。

一个J2ME运行环境名称包括配置和设备简表。在J2ME中,下面是两个不同的运行环境:

microedition.configuration microedition.profiles

例如,Foundation有这样一个命名的运行环境:CDC- 1.0/Foundation- 1.0,命名结构采用以下的规则:

http://assets.osgi.com.cn/article/7289393/10.png

配置和设备简表由JCP或者是OSGi联盟定义,一个运行环境不能没有配置或者设备简表,设备简表用于区别不同运行环境。这些原则并不是标准化的。

下表展示了部分常见的运行环境描述:

http://assets.osgi.com.cn/article/7289393/11.png

org.osgi.framework.executionenvironment属性来自于BundleContext.getProperty(String),必须要包含在框架的运行环境中。这个属性是易变的。由于bundle有可能随时会改变这个属性值,因此框架实现者不能缓存这个信息。这样做的目的是为了进行测试和对运行时的执行环境的可扩展性。

3.4. 类加载机制

许多bundle可以共享虚拟机(VM)。在VM内部,bundle可以相互隐藏包和类,也可以和其他bundle共享包。

隔离和共享包关键是由java的类加载器来实现,类加载器通过仔细定义的规则从bundle空间的一个子集中加载类。每一个bundle只会有一个单独的类加载器,类加载器形成了一个类加载的代理网络结构,如下所示:

http://assets.osgi.com.cn/article/7289393/12.png

类加载器可以加载类和资源,加载途径有:

  • 启动类路径:启动类路径中有一个java.*的包以及它实现的包。
  • 框架类路径:在框架中通常有一个单独的类加载器,加载框架实现的类和关键的服务接口类。
  • Bundle类空间:bundle的类空间由和bundle相关的JAR文件组成,以及其他和bundle紧密相关的JAR文件,比如bundle片断(参考bundle片断一节)

类空间是指一个给定的bundle类加载器可以访问到的所有的类。因此,一个指定bundle的类空间来自:

  • 父类加载器(通常是来自启动类路径的java.*包中的)
  • 导入的包
  • 必须的bundle
  • Bundle类路径(私有包)
  • 附加的片断

类空间必须是一致的,也就是说不能存在相同全名的两个类(为了防止类声明错误)。但是,在OSGi框架中,不同的类空间可以存在同名的类。在模块层,支持不同版本的类加载到相同的虚拟机中。

http://assets.osgi.com.cn/article/7289393/13.png

在类加载过程中,框架需要完成一系列的职责。在使用一个bundle之前,必须对共享的包之间的约束关系进行解析。选择一个最合适的类并创建连接(wiring),想请参考解析过程一节。在运行时类加载一节中也描述了一些运行时的特性。

3.4.1. 解析过程

框架必须支持bundle的解析。解析过程就是确定导入包如何连接到导出包( importers are wired to exporters)。解析过程需要满足约束条件。解析过程必须在bundle中任何代码加载或运行之前。

连接线是指导入包和导出包(都是bundle)之间的实际联系,连接线关联到一系列的约束,这些约束由导入导出包的manifest文件进行定义。一个有效的连接是满足它所有约束的连接。下图描述了连接模型的类结构。

http://assets.osgi.com.cn/article/7289393/14.png

3.5. 元数据处理

本节描述了manifest为解析过程提供的元数据信息。

3.5.1. Bundle- ManifestVersion

bundle的manifest文件必须在名称为Bundle- ManifestVersion的头标中提供,它遵循OSGi manifest header语法。采用本规范或者后续版本规范的bundle必须要指定这个头标。头标的结构如下所示:

http://assets.osgi.com.cn/article/7289393/15.png

符合框架版本1.3的bundle的manifest版本必须是‘2’,在1.3之前版本的bundle的manifest版本为‘1’,虽然在这样的manifest中无法表述版本。因此,如果版本值不为‘2’,除非框架明确支持这样的后续版本,否则都是无效的标记。

OSGi框架的实现也可以支持没有Bundle- ManifestVersion头标的manifest,这样可以和框架1.2兼容。

版本号为‘2’的bundle必须指定bundle的符号名称(Symbolic Name)。而不需指定bundle的版本,这个头标有一个默认值。

3.5.2. Bundle- SymbolicName

头标Bundle- SymbolicName(符号名称)是必须指定的。通过bundle的符号名称和版本号可以在框架中惟一的确定一个bundle。也就是说,如果一个bundle和另外一个bundle有着同样的符号名称和版本号,那么这两个bundle就是等价的。

如果一个bundle的符号名称和版本号与已有的bundle相同,那么对这个bundle的安装肯定是失败的。

bundle的符号名称由开发者确定(manifest中的头标Bundle- Name是设计为人可读的,因此这个头标不会代替Bundle- Name)。

bundle的符号名称标记必须采用以下的规则:

http://assets.osgi.com.cn/article/7289393/16.png

框架必须识别在头标Bundle- SymbolicName中的以下指令:

  • singleton:表示这个bundle只有一个版本可以解析。如果这个值为true,那么表示是一个单态的bundle。它的默认值是为false。当同名且设为单态的bundle的多个版本同时安装在框架中,那么框架最多只能对一个bundle进行解析。如果两个bundle的符号名称相同,那么单态的bundle并不会影响到非单态的bundle的处理。

  • fragment- attachment:定义了哪些片断(fragment)可以附加到bundle上。参考bundle片断一节。下面定义了哪些值是合法的fragment- attachment值:

    - always:片断可以在附主(host)解析完成之后,或者是在解析过程中附加。
    - never:不允许附加片断。
    - resolve- time:只允许在解析过程中附加。
    

示例:

http://assets.osgi.com.cn/article/7289393/17.png

3.5.3. Bundle- Version

Bundle- Version 是一个可选的头标,它的默认值是0.0.0。如下例:

http://assets.osgi.com.cn/article/7289393/18.png

如果小版本号(minor)和微版本号(micro)没有指定,那么他们的默认值为0。如果限定部分()没有指定,那么默认值为一个空字符串。

版本是可以进行比较的,比较是根据主版本号、小版本号、微版本号的顺序进行比较。最后是字符串的限定符比较。

例如:

http://assets.osgi.com.cn/article/7289393/19.png

3.5.4. Import- Package

Import- Package头标定义了共享包的导入约束。本头标的规则如下:

http://assets.osgi.com.cn/article/7289393/20.png

在这个头标中可以定义多个导入包。每条记录描述一个单独的导入包。其中指令如下:

  • resolution:如果这个值为mandatory,这也是默认值,那么这个包必须要解析,如果不能解析,那么对bundle的解析也会失败。如果这个值为optional,那么这个导入包是可选的。参考可选导入包一节 开发人员可以指定任意匹配的属性,参阅属性匹配一节。下面预定义了任意匹配属性:

  • version:定义了导入包的版本范围。字段必须遵循版本范围一节中描述的规则。详情参阅版本匹配一节。如果没有指定这个属性,默认值为[0.0.0, ∞)。

  • specification- version:是version属性的同义词,只是为了兼容原来的框架版本。如果指定了version属性,那么这两个值必须相等。

  • bundle- symbolic- name:导出bundle的符号(symbolic)名称。

  • bundle- version:选择导出bundle的版本范围。默认值为[0.0.0, ∞),参阅版本匹配一节

为了允许导入包(除了以java开头的包之外),bundle必须有PackagePermission[,IMPORT] 参阅包权限一节。

以下错误会导致安装和更新的终止:

  • 同样的指令或属性重复出现多次

  • 同样的包进行多次导入定义

下面是一个正确的定义:

http://assets.osgi.com.cn/article/7289393/21.png

3.5.5. Export- Package

Export- Package头标和上述的Import- Package的规则类似。只是属性和指令不同。

http://assets.osgi.com.cn/article/7289393/22.png

在头标中可以定义多个导出包。一个导出定义描述了一个bundle的导出包。可以对同一个包的多个导出定义,对不同的importer定义不同的属性。

导出指令如下:

uses:定义导出包所用到的包清单,采用逗号分隔包名。注意,逗号需要用双引号包括起来。如果对导出包进行导出,那么解析器必须保证这个包的导入连接到了本清单中描述的相同的版本的包。参阅包约束一节。

  • mandatory:逗号分隔的属性名称清单。同样,逗号需要用双引号包括起来。导入这个包的bundle必须指定mandatory值描述的属性,以便于进行导入包的解析。参考强制属性一节。

  • include:逗号分隔的类名清单,描述对importer必须可见的类。逗号需要用双引号包括起来。参考类过滤一节。

  • exclude:描述对importer必须是不可见的类和资源名称清单,采用逗号分割,逗号需要用双引号包括起来。参阅类过滤一节。

下面的属性是本规范的一部分:

  • version:描述包的版本,定义了相关包的版本,默认值为0.0.0。
  • specification- version:版本的别名,为了支持以前的版本。如果指定了version属性,那么这两个值必须相等。

另外,任意匹配的属性也可以进行说明。参阅属性匹配一节。

框架自动关联每一个定义了如下属性的导出包:

  • bundle- symbolic- name:导出bundle的符号名称。
  • bundle- version:导出bundle的版本号。

如果以下任何条件为真,那么将对导致安装或者更新的终止:

  • 一条指令或者属性出现多次。
  • 在头标Export- Package中指定了bundle- symbolic- name或者bundle- version

在导出定义中没有隐含自动导入定义。如果一个bundle导出一个包而没有导入这个包,这个bundle将通过bundle类路径获得这个包。只有导出的包可以用于其它bundle,但是导出的bundle不能使用来自其它bundle的同样的包。

为了导出一个包,bundle必须有以下字段:

PackagePermission[, EXPORT]。

例如:

http://assets.osgi.com.cn/article/7289393/23.png

3.5.6. 导入和导出一个包

导出一个包并不意味了导入同样的包(在OSGi R3中,导出意味着导入同样的包)。这样改进的原因在于使得bundle可以对其他bundle提供包,而无需考虑导出包由解析器从其它bundle中用同样的包来替代。如果一个应用是由一系列的相互关联紧密的bundle组成,而在这些bundle中,实现包提供给其他bundle,那么这样的情况是很常见的。

在包的互操作中,这种包的替代至关重要。在java中,只有bundle中同样的类使用相同的类加载器,那么bundle才能进行互操作(inter- operate)。因此,如果两个bundle导出了同样的包,但是没有导入这个包,那么这两个bundle就不能共享这个包中的对象。这一点对于协作机制非常重要。例如在服务层中,如果bundle的服务对象是来自同样的类加载器,那么他们只能使用同样的服务对象。

bundle应该导入已经导出的包,允许解析器替代包含了接口和其他共享类型的包。这种替代允许bundle通过服务注册和其他机制进行互操作。另外,为了使解析器保持最大限度的灵活性,导入者应该尽可能的减少约束。

3.5.7. 对遗留bundle的处理

如果在头标Bundle- ManifestVersion中没有指定值为2或者更大数,那么就根据发布的版本3(R3)中的定义来解释。也就是说,框架必须根据R3头标定义转换为R4头标:

  • Import- Package:将specification- version转换为version属性。如果没有定义这个属性,那么就无需转换,因为R3中的version属性默认值为0.0.0,具有相同的语义。

  • Export- Package:将specification- version转换为version属性。导出的定义中必须含有uses指令,uses指令必须包含所有的给定bundle的导入导出包。另外,如果这个包没有导入定义,那么必须添加给定版本的导入定义。

  • DynamicImport- Package: 定义没有修改。

如果在bundle的manifest中version属性标记为2,但是又同时存在着遗留语法标记,那么这个错误会导致安装失败。

在版本为2的manifest中,不赞成使用specification- version属性。

3.6. 约束处理

OSGi框架包解析器提供了一系列的导入与导出匹配的机制。本节描述了这些详细的机制。

3.6.1. 图表和标记

线(wire)将节点(nodes)关联到一起形成了图(graph)。线和节点(即bundle)一样,包含了大量重要的信息。下一节,采用以下约定来解释一些细节:

bundle的名称为A、B、C等,也就是从A开始的大写字母。用p、q、r、s、t等命名包。也就是从p开始的小写字母。如果一个版本很重要,那么版本的标记后加上一个短划线,如:q- 1.0。标记A.p表示在名称为A的bundle中定义(导入或者导出)了包p。

采用白色方框表示导入定义,黑色方框表示导出定义,没有导入导出的包称之为私有包,用斜线网格背景表示。

Bundle是一系列关联的方框的集合。Bundle之间的关联用线(wire)表示,而约束写在这些线上。

http://assets.osgi.com.cn/article/7289393/24.png

例如:

http://assets.osgi.com.cn/article/7289393/25.png

可以用下图来描述A、B、C这三个bundle:

http://assets.osgi.com.cn/article/7289393/26.png

3.6.2. 版本匹配

在版本机制中,对匹配导出定义的版本和版本范围采用了精确的描述。版本范围的编码考虑了兼容性。本规范没有定义任何兼容性原则,而留待importer制定一个版本范围。一个版本的范围嵌入了兼容性的原则。

但是,最常见的版本兼容原则如下:

  • 主版本号(major):不兼容的更新
  • 副版本号(minor):向后兼容的更新
  • 小版本号(micro):不影响接口的更新。例如,修正了一个错误。

一个导入定义必须指定一个版本范围作为导入的version属性值,exporter必须指定一个版本号作为它的version属性。根据版本范围一节中的描述进行版本的匹配。

在下例中,A中的导入和B中的导出可以正确进行匹配。

http://assets.osgi.com.cn/article/7289393/27.png

下图描述了根据规则排除exporter。

http://assets.osgi.com.cn/article/7289393/28.png

3.6.3. 可选包

在bundle中可以指定它不需要正确的解析一个包,但是如果这个包可用就使用它。例如,登录非常重要,但是如果没有登录服务,bundle也应该可以运行的。

可选导入包可以通过以下方法指定

  • Dynamic Imports动态导入:DynamicImport- Package头标描述了需要的导出包。如果bundle在调用之前不知道类名,关键技术是通过使用类的forName方法来加载类。

  • Resolution Directive resolution指令:在导入定义中通过resolution指令指定一个可选值。如果没有合适的可选包,那么bundle也可以成功解析。

这两种机制的区别是:

  • 可选 vs 动态:对于动态导入,每次加载包中的类都会尝试与动态导入包进行建立连接,而可选导入包只有在bundle解析的时候才进行连接尝试。

导入定义的resolution指令中,它的值可以为mandatory(强制的)或者是optional(可选的)。

  • mandatory:默认值,指定包必须连接到bundle。

  • optional:指定在包没有连接时也可以对导入bundle进行解析。

下例中,即使bundle B没有提供正确的匹配版本,bundle A 也可以进行解析。

http://assets.osgi.com.cn/article/7289393/29.png http://assets.osgi.com.cn/article/7289393/30.png

bundle的实现中,必须要考虑到可选包没有加载的情况。比如,抛出一个相关的找不到包的异常。

3.6.4. 包约束

一个类可以依赖于其他包中的类。例如,从其他包中的类继承,或者这些类出现在方法的声明中。这种情况称之为一个包使用了其他包。这种包之间的关系通过在Export- Package头标中使用uses指令来描述。

例如:org.osgi.service.http使用包javax.servlet中的API,所以这两个包存在依赖关系。在包org.osgi.service.http中必须包含值为javax.servlet的uses指令。

只有设置bundle中每一个包都只有一个exporter,才可以保证类空间的一致性。

例如,Http服务的实现需要从javax.servlet.http.HttpServlet中继承的servlets类,如果Http服务bundle导入version值为2.4而客户端bundle导入version为2.1,那么必然发生类声明错误。如下所示:

http://assets.osgi.com.cn/article/7289393/31.png

如果bundle从一个exporter中导入了一个包,那么在导出定义中可以通过uses指令隐含一系列对其他包的约束。uses指令列出了exporter依赖的一系列的包,这些约束保证了这一系列的bundle对于相同的包共享同样的类加载器。

如果一个导入者导入了含有约束的包,解析器必须通过约束建立importer和exporter之间的连接(wire)。而exporter可能也有同样的约束。这样,建立单个导入包和exporter之间的连接可以隐含有一大串的约束。术语导出包约束(implied package constraints)是指通过这种递归循环构建的约束集合,也隐含包约束并不包括自动导入,进一步说,隐含包约束只包括了在导入定义中描述的必须解析的内容。

如下图所示,bundle A导入了包p,假设定义的p连接到bundle B。由于uses指令(在此省略uses指令描述)隐含了对包q的约束。进一步说,假设对包q的导入关联到bundle C,那么也就隐含了对包r和s的约束。接下来,假设C.s和C.r分别连接到bundle D和E,这两个bundle都将包t添加到bundle A的依赖包集合中。

http://assets.osgi.com.cn/article/7289393/32.png

为了维护类空间的一致性,框架必须确保bundle的导入不能和bundle依赖包中存在冲突。

例如,这也就是说框架必须保证A.t的导入定义连接到D.t。如果导入定义连接到包F.t,那么就违背了类空间的一致性。这是由于在bundle A中,同时存在着来自bundle D和F的两个同名的类。这将导致一个类声明异常(ClassCastExceptions)。或者,如果所有的bundle连接到F.t,也同样可以解决问题。

另一种情况是图21所示,bundle A从B中导入了Http服务类,bundle B中包含了org.osgi.service.http和javax.servlet,bundle A和bundle B同样的存在连接到javax.servlet的约束。

下面的示例说明了通过对uses指令的设置,使得不可能进行解析。

http://assets.osgi.com.cn/article/7289393/33.png

由于A.q可以连接到B.q而不是C.q,这种约束可以解析的。

添加bundle D之后就不可能解析了:

http://assets.osgi.com.cn/article/7289393/34.png

由于bundle A是p惟一的exporter,包D.p必须连接到包A.p。但是,根据包A.q中的uses指令隐含了包q,包A.q连接到B.q- 1.0,而D.q需要版本为2.0的包,由于违背了类空间约束的导致解析失败。

下图描述了这种情况:

http://assets.osgi.com.cn/article/7289393/35.png

3.6.5. 属性匹配

属性匹配允许importer和exporter通过说明性的途径来影响匹配过程。为了使得导入定义属性可以解析为导出定义,导入中定义的属性必须要和导出中定义的属性匹配。默认情况下,如果导出定义中包含的属性没有出现在导入定义中,匹配过程继续进行。如果在导出定义中指定了mandatory指令,则框架在导入定义必须要匹配这些属性。在解析阶段,对于出现在字段DynamicImport- Package中的任何信息都是忽略处理。

例如,下例的语句是匹配的:

http://assets.osgi.com.cn/article/7289393/36.png

除了字段version和bundle- version,其他的属性值都是通过字符串方式进行比较。这两个字段采用版本范围的比较方法。属性中开始和结尾部分的空格都是忽略不计的。

3.6.6. 强制属性

属性分为两种:mandatory(强制的) 和optional(可选的)。mandatory属性表示必须匹配的属性。optional属性表示可以在导入时不考虑的属性,默认值为optional。

exporter可以在导出定义中通过mandatory指令指定强制属性,这条指令包含了一个逗号分割的属性名称列表,表示必须在导入描述中匹配的属性。如下例所示,导入包和导出包不是匹配的,由于在A中没有指定security属性。

http://assets.osgi.com.cn/article/7289393/37.png

3.6.7. 类过滤

exporter可以通过在导出定义中使用include和exclude指令来限制对类的访问范围。这两条指令的值都是逗号分割的类名列表。注意,逗号需要用双引号包括起来。类名不能包含有包名称,而且不能含有后缀.class。也就是说,类com.acme.foo.Daffy在指令中名称为Daffy。类名称中可以含有通配符(’*’)。

对于include指令,它的默认值为通配符’*’(表示所有的名称),也就是说所有的类和资源。而exclude指令的默认值为空,一个空的名称表示没有匹配的名称。如果指定了这两个指令的值,那么默认值就被覆盖了。

如果满足以下条件,一个类是可见的:

  • 在include中一条记录匹配,而且
  • 在exclude中没有记录可以匹配

在其他情况下,加载或者搜索失败,类加载器抛出一个无法找到类的异常(Class Not Found Exception)。在指令中,不考虑排列顺序。

下例列出了一个导出段和其中文件的可见性。

http://assets.osgi.com.cn/article/7289393/38.png

使用过滤器时必须小心。例如,模块的一个新版本需要和旧版本兼容,那么就不应该将旧版本中没有排除的类和资源排除在外(exclude)。另外,模块化现有的代码时,从导出包中排除的类和资源有可能中断和包连接的用户。例如,正是定义的包通常在正式包中有一个实现类,而正式包对定义包有访问权限。

package org.acme.open;
public class Specified {
    static Specified implementation;
    public void foo() { implementation.foo(); }
}

package org.acme.open;
public class Implementation {
    public void initialize(Specified implementation) {
        Specified.implementation = implementation;
}
}

由于实现类允许设置,因此对于扩展bundle必须是不可见的。可以通过将实现类排除在外,使得只有导出bundle可以访问这个类。导出定义的标记描述如下:

http://assets.osgi.com.cn/article/7289393/39.png

3.6.8. 选择提供者(Provider selection)

提供者选择(provider selection)”指允许importer选择一个bundle来作为exporter。如果在一个importer和exporter之间没有规格约束,则进行“提供者选择”。importer与一个特定的exporter紧密关联,一般情况下是用于测试的bundle。为了降低连接的脆弱性,importer可以指定一个可以选择的范围。

importer可以通过导入属性bundle- symbolic- name和bundle- version选择一个exporter。框架自动为每个导出定义(export definition)提供了这些属性(attributes)。这些属性不能在导出定义中进行指定。

导出定义的bundle- symbolic- name属性包含了bundle的符号名称,它是在头标Bundle- SymbolicName中不带参数进行指定的。导出定义中的bundle- version属性被设置为头标Bundle- Version的值,或者默认值为0.0.0。

属性bundle- symbolic- name的匹配采用属性匹配方法。bundle- version的匹配采用版本范围比较方法,在版本范围一节中有详细描述。导入定义必须有一个版本范围而导出定义是一个版本号。

如下例,定义是可以匹配的:

http://assets.osgi.com.cn/article/7289393/40.png

下面的例子是不能进行匹配的,B没有指定一个版本,取其默认值为0.0.0。

http://assets.osgi.com.cn/article/7289393/41.png

通过符号名称选择一个exporter可能会导致脆弱性,这是由于将包和bundle强耦合在一起。例如,如果一个exporter被重构为多个单独的bundle,那么所有涉及的importer都需要进行修改。其他的任意匹配属性由于是独立的,所以没有这样的缺陷。

Bundle重构时由符号名称带来的脆弱性问题,可以通过使用一个与原bundle同名称的fa?ade模式bundle来取得部分的解决。

3.7. 解析过程

解析过程就是建立bundle之间的连接(wire)的过程。连接之间的约束由以下条件静态定义:

  • 导入导出包(此阶段不考虑DynamicImport- Package的内容)

  • required bundles,需要的bundle,它们导入了bundle所有的导出包,具体定义参见后文的“bundle需求”一节

  • Fragments,片断,向宿主提供内容和定义,在bundle片断一节有详细定义

在bundle被解析之前,它所有的片断都必须已经attach到bundle之上。解析过程就变成了一个约束求解算法,可以使用“关联关系的要求”进行描述。解析处理就是对解空间的迭代搜索过程。

如果一个模块(module)对同一个包有导入和导出定义,那么框架需要判断选择哪一个定义。 必须首先处理重复的导入定义。下面是可能的结果:

  • 外部(external)—如果解析到另一个bundle的导出定义,那么不考虑在这个bundle中的重复导出定义。

  • 内部(internal)—如果解析到了这个模块中的导出定义,那么不考虑这个模块的重复导入定义。

  • 无法解析—没有匹配的导出定义。这是由于开发者的错误,也就是说重复的导出定义和对应的导入定义不相匹配。

如果满足以下条件,bundle可以进行解析:

  • 所有的必需的导入都已经连接

  • 所有required bundle都是可用的而且建立了他们的导出连接。

而连接的建立需要满足以下条件:

  • importer的版本范围和exporter的版本匹配。参见版本匹配的说明。

  • importer具有exporter指定的所有的强制属性。参见强制属性说明一节。

  • importer的所有的属性和exporter的相应属性匹配。参见属性匹配一节。

  • 如果连接到同一个exporter,那么也就隐式关联到同一个包。参见包约束一节。

  • 连接关联到一个合法的exporter。

下面列表定义了优先级别,如果有多个选择,根据优先级降序排列:

  • 一个已经解析的exporter优先于一个未解析的exporter。

  • 具有更高版本的exporter优先于一个低版本的exporter。

  • 具有较低bundle ID的exporter优先于较高ID的exporter。

查看评论