在学习 OSGi 时,首先要学习的概念之一是“捆绑包”的概念。在本篇文章中,我想更仔细地了解捆绑包究竟是什么,以及如何将一个普通的 jar 文件转换为 OSGi 捆绑包。所以,事不宜迟,
什么是捆绑包?
OSGi 规范将捆绑包描述为“模块化的单元”,它“由 Java 类和其他资源组成,这些资源 together 可以为最终用户提供功能”。到目前为止还不错,但是捆绑包究竟是什么?再次引用规范:
捆绑包是一个 JAR 文件,它
- 包含 [...] 资源
- 包含一个描述 JAR 文件内容并提供有关捆绑包信息的清单文件。
- 可以在 JAR 文件的 OSGI-OPT 目录或其子目录之一中包含可选文档。
简而言之,一个捆绑包 = jar + OSGi 信息(在 JAR 清单文件中指定 - META-INF/MANIFEST.MF),不需要额外的文件或预定义的文件夹布局。这意味着从 jar 创建捆绑包所需做的就是向 JAR 清单添加一些条目。
OSGi 元数据
OSGi 元数据由清单条目表示,这些条目指示 OSGi 框架捆绑包提供和/或需要什么。规范指出了大约 20 个清单头,但我们只看看最常用的那些。
Export-Package
顾名思义,此标题指示导出哪些包(在捆绑包中可用),以便其他捆绑包可以导入它们。*只有*标题指定的包才会被导出,其余的将是私有的,并且在包含捆绑包之外不可见。
Import-Package
类似于Export-Package,此标题指示捆绑包导入的包。同样,*只有*此标题指定的包才会被导入。默认情况下,导入的包是必需的 - 如果导入的包不可用,则导入捆绑包将无法启动。
Bundle-SymbolicName
唯一必需的标题,此条目指定捆绑包的唯一标识符,基于反向域名约定(也由 java 包使用)。
Bundle-Name
定义此捆绑包的可读名称(无空格)。建议设置此标题,因为它可以提供比
Bundle-SymbolicName更短、更有意义的关于捆绑包内容的信息。
Bundle-Activator
该
BundleActivator是一个 OSGi 特定的接口,允许在 OSGi 框架启动或停止捆绑包时通知 Java 代码。此标题的值应包含激活器类的完全限定名称,该类应该是公共的,并且包含没有任何参数的公共构造函数。
Bundle-Classpath
当 jar 包含嵌入式库或位于不同文件夹下的类包时,此标题非常方便,方法是扩展默认的捆绑包类路径(预期类直接在 jar 根目录下可用)。
Bundle-ManifestVersion
这个鲜为人知的标题指示用于读取此捆绑包的 OSGi 规范。
1表示 OSGi 版本 3,而
2表示 OSGi 版本 4 及更高版本。由于
1是默认版本,因此强烈建议指定此标题,因为 OSGi 版本 4 捆绑包在 OSGi 版本 3 下不会按预期工作。
以下是一个示例,摘自使用上面提到的某些标题的 Spring 2.5.x 核心捆绑包清单
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 包条目上,因为它们描述了捆绑包(即模块)之间的关系。在包方面,没有什么是不言自明的 - 只有提到的包才会被导入/导出,其余的不会。这也适用于子包:导出org.mypackage将*仅*导出此包,而不会导出其他任何内容(例如org.mypackage.util)。导入也是如此 - 即使某个包在 OSGi 空间中可用,除非某个捆绑包明确导入它,否则该捆绑包将看不到它。
总而言之,如果捆绑包 A 导出包 org.mypackage,而捆绑包 B 想使用它,那么捆绑包 A 的META-INF/MANIFEST.MF应该在其Export-Package标题中指定该包,而捆绑包 B 应该将其包含在其Import-Package条目中。
包注意事项
导出相当简单,但导入稍微复杂一些。应用程序通常会通过搜索环境中的某些库并仅使用可用的库来很好地降级,或者库会包含用户不使用的代码。此类示例包括日志记录(使用 JDK 1.4 或 Log4j)、正则表达式(Jakarta ORO 或 JDK 1.4+)或并发实用程序(JDK 5 中的 java.util 或backport-util-concurrent库用于 JDK 1.4)。
用 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格式。
手工操作
不建议采用这种DIY方法,因为拼写错误和多余的空格很容易混入并导致清单文件失效。即使使用智能编辑器,清单文件的格式本身也可能导致一些问题,因为它每行最多只能有72个空格,如果超过这个限制,可能会导致一些难以理解的问题。手动创建或更新jar文件不是一个好主意,因为jar格式要求META-INF/MANIFEST.MF条目必须是存档中的第一个条目 - 如果不是,即使它存在于jar文件中,清单文件也不会被读取。手动方法只推荐在没有其他选择的情况下使用。
但是,如果真的需要直接处理清单文件,则应使用可以处理UNIX/DOS空格的编辑器以及合适的jar创建工具(例如JDK附带的jar工具)来满足所有MANIFEST的要求。
Bnd
Bnd 代表BuNDle工具,是由Peter Kriens(OSGi技术主管)创建的一个不错的实用程序,它“帮助……创建和诊断OSGi R4 bundle”。Bnd解析java类以了解可用的和导入的包,以便它可以创建等效的OSGi条目。Bnd提供了一系列指令和选项,可以自定义生成的工件。bnd.jar本身的好处是可以从命令行运行,通过专用的任务通过Ant运行,或作为插件集成到Eclipse中。
Bnd可以从类路径或Eclipse项目中可用的类创建jar文件,也可以通过添加必要的OSGi工件来将现有的jar文件转换为osgi格式。此外,它可以打印和验证给定jar文件的OSGi信息,使其成为一个功能强大且易于使用的工具。
第一次使用的用户可以使用Bnd查看将添加到普通jar文件的OSGi清单。让我们选择一个普通的jar文件,例如c3p0(这是一个优秀的连接池库),并发出打印命令
```code java -jar bnd.jar print c3p0-0.9.1.2.jar ```
输出相当大,包含几个部分
- 通用清单信息
[MANIFEST c3p0-0.9.1.2.jar]
Ant…