在接触 OSGi 时,首先必须学习的概念之一就是bundle的概念。在这篇文章中,我想仔细看看 bundle 到底是什么,以及一个普通的 jar 如何转换为 OSGi bundle。 那么,话不多说,
什么是 bundle?
OSGi 规范将 bundle 描述为“模块化单元”,它“由 Java 类和其他资源组成,这些资源共同可以为最终用户提供功能。”到目前为止还好,但 bundle 究竟是什么?再次引用规范
一个 bundle 是一个 JAR 文件,它
- 包含 [...] 资源
- 包含一个 manifest 文件,描述 JAR 文件的内容并提供关于 bundle 的信息
- 可以在 JAR 文件的 OSGI-OPT 目录或其子目录中包含可选文档
简而言之,一个 bundle = jar + OSGi 信息(在 JAR manifest 文件 - META-INF/MANIFEST.MF 中指定),不需要额外的文件或预定义的文件夹布局。这意味着从一个 jar 创建一个 bundle 所需的全部工作就是向 JAR manifest 添加一些条目。
OSGi 元数据
OSGi 元数据由 manifest 条目表示,这些条目向 OSGi 框架指示 bundle 提供和/或需要什么。规范指出了大约 20 个 manifest 头,但我们只会看一些您最有可能使用的头。
Export-Package
顾名思义,此头指示导出哪些(在 bundle 中可用的)包,以便其他 bundle 可以导入它们。只有此头指定的包会被导出,其余的将是私有的,并且在包含此 bundle 的外部不可见。
Import-Package
与 Export-Package 类似,此头指示 bundle 导入的包。同样,只有此头指定的包会被导入。默认情况下,导入的包是强制性的——如果导入的包不可用,导入的 bundle 将无法启动。
Bundle-SymbolicName
唯一的必选头,此条目根据反向域名约定(Java 包也使用此约定)为 bundle 指定一个唯一的标识符。
Bundle-Name
为此 bundle 定义一个人类可读的名称,不含空格。建议设置此头,因为它比
Bundle-SymbolicName 能提供更短、更有意义的关于 bundle 内容的信息。
Bundle-Activator
该
BundleActivator 是一个 OSGi 特定的接口,它允许 Java 代码在 bundle 由 OSGi 框架启动或停止时接收通知。此头的值应包含激活器类的完全限定名,该类应是公共的且包含一个不带任何参数的公共构造函数。
Bundle-Classpath
当 jar 包含各种文件夹下的嵌入式库或类包时,此头非常方便,它通过扩展默认的 bundle 类路径(默认类路径期望类直接位于 jar 根目录下)来实现。
Bundle-ManifestVersion
这个鲜为人知的头指示用于读取此 bundle 的 OSGi 规范版本。
1 表示 OSGi Release 3,而
2 表示 OSGi Release 4 及更高版本。由于
1 是默认版本,强烈建议指定此头,因为 OSGi Release 4 的 bundle 在 OSGi Release 3 下无法按预期工作。
下面是一个示例,取自 Spring 2.5.x 核心 bundle 的 manifest 文件,使用了上面提到的一些头
Bundle-Name: spring-core
Bundle-SymbolicName: org.springframework.bundle.spring.core
Bundle-ManifestVersion: 2
Export-Package:org.springframework.core.task;uses:="org.springframework.core,org.springframework.util";version=2.5.1 org.springframework.core.type;uses:=org.springframework.core.annotation;version=2.5.1[...]
Import-Package:org.apache.commons.logging,edu.emory.mathcs.backport.java.util.concurrent;resolution:=optional[...]
花在 OSGi 元数据上的大部分时间可能都用于 Export/Import 包条目,因为它们描述了 bundle 之间的关系(即您的模块之间的关系)。对于包来说,没有任何隐式规则——只有提及的包才会被导入/导出,其余的则不会。这也适用于子包:导出 org.mypackage 将只导出这个包,而不是其他任何东西(例如 org.mypackage.util)。导入也是一样——即使一个包在 OSGi 空间中可用,除非某个 bundle 明确导入它,否则它将不可见。
总结一下,如果 bundle A 导出包 org.mypackage 并且 bundle B 想要使用它,那么 bundle A 的 META-INF/MANIFEST.MF 应在其 Export-Package 头中指定该包,而 bundle B 应将其包含在其 Import-Package 条目中。
包的考虑
导出相当直接,但导入稍微复杂一些。应用程序通常会通过搜索环境中的特定库并仅使用可用的库来良好地降级,或者库包含用户未使用的代码。这些例子包括日志记录(使用 JDK 1.4 或 Log4j)、正则表达式(Jakarta ORO 或 JDK 1.4+)或并发工具(JDK 5 中的 java.util 或用于 JDK 1.4 的 backport-util-concurrent 库)。
用 OSGi 的术语来说,根据包的可用性来依赖它,这等同于一个可选的 Package-Import。您已经在前面的示例中看到过这样的包
```code Import-Package: [...]edu.emory.mathcs.backport.java.util.concurrent;resolution:=optional ```
由于在 OSGi 中,可以存在同一个类的多个版本,因此最佳实践是在导出和导入包时都指定类包的版本。这是通过在每个包声明后添加 version 属性来完成的。OSGi 支持的版本格式是 <major>.<minor>.<micro>.<qualifier>,其中 major、minor 和 micro 是数字,qualifier 是字母数字。
该版本含义完全由 bundle 提供者决定,但建议使用流行的编号方案,例如 Apache APR 项目的方案,其中
- <major> - 表示重大更新,不保证兼容性
- <minor> - 表示保留与旧次版本兼容性的更新
- <micro> - 表示从用户角度来看无关紧要的更新,完全向前和向后兼容
- <qualifier> - 是用户定义的字符串 - 不广泛使用,可以为版本号提供额外的标签,例如构建号或目标平台,没有标准化的含义
默认版本(如果属性缺失)是“0.0.0”。
导出的包必须指定特定版本,而导入者可以使用数学区间表示法指定范围 - 例如
[1.0.4, 2.0) 将匹配版本 1.0.42 及以上直至 2.0(不包含)。请注意,如果只指定一个版本而不是区间,将匹配所有大于或等于指定版本的包,即
Import-Package: com.mypackage;version="1.2.3"
等价于
Import-Package: com.mypackage;version="[1.2.3, ∞)"
最后一点提示,指定版本时,无论是范围还是单个版本,请务必始终使用引号。
使用 OSGi 元数据
既然我们已经了解了 bundle 的一些信息,接下来看看可以使用哪些工具来将现有 jar OSGi 化
手动方式
不建议采用这种手动方式,因为很容易出现打字错误和多余空格,导致 manifest 文件失效。即使使用智能编辑器,manifest 格式本身也可能引发一些问题,因为它规定每行最多 72 个空格,如果超出则可能导致一些难以理解的问题。手动创建或更新 jar 也不是一个好主意,因为 jar 格式要求 META-INF/MANIFEST.MF 条目必须是归档中的第一个——如果不是,即使它存在于 jar 中,manifest 文件也不会被读取。手动方式只在没有其他替代方案的情况下才真正推荐使用。
然而,如果确实想要/需要直接处理 manifest 文件,则应使用能够处理 UNIX/DOS 空格的编辑器,并结合适当的 jar 创建工具(例如 JDK 自带的 jar 工具),以满足所有 MANIFEST 要求。
Bnd
Bnd 是 BuNDle tool 的缩写,是一个由 Peter Kriens(OSGi 技术官员)创建的实用工具,它“帮助 [...] 创建和诊断 OSGi R4 bundle”。Bnd 解析 Java 类以了解可用和导入的包,从而创建相应的 OSGi 条目。Bnd 提供了一系列指令和选项,可以自定义生成的 artifact。bnd.jar 本身的优点在于它可以从命令行运行,通过 Ant 的专用任务运行,或作为插件集成到 Eclipse 中。
Bnd 可以从类路径中或 Eclipse 项目内的类创建 jar,也可以通过添加所需的 OSGi artifacts 来将现有 jar OSGi 化。此外,它可以打印和验证给定 jar 的 OSGi 信息,使其成为一个功能强大但易于使用的工具。
初次使用的用户可以使用 Bnd 查看普通 jar 会被添加哪些 OSGi manifest 条目。让我们选择一个普通 jar,例如 c3p0(这是一个出色的连接池库),然后执行打印命令
```code java -jar bnd.jar print c3p0-0.9.1.2.jar ```
输出内容相当多,包含多个部分
- 通用 manifest 信息
[MANIFEST c3p0-0.9.1.2.jar]
Ant…