领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多在学习 OSGi 时,首先要了解的概念之一是捆绑包的概念。在本篇博文中,我想更仔细地研究一下捆绑包到底是什么,以及如何将一个普通 jar 文件转换为 OSGi 捆绑包。所以,事不宜迟,
OSGi 规范将捆绑包描述为“模块化的单元”,它“由 Java 类和其他资源组成,这些资源可以共同为最终用户提供功能”。到目前为止,一切都很好,但究竟什么是捆绑包?再次引用规范
捆绑包是一个 JAR 文件,它
- 包含 [...] 资源
- 包含一个清单文件,描述 JAR 文件的内容并提供有关捆绑包的信息
- 可以在 JAR 文件的 OSGI-OPT 目录或其子目录之一中包含可选文档
简而言之,捆绑包 = jar + OSGi 信息(在 JAR 清单文件中指定 - META-INF/MANIFEST.MF),不需要额外的文件或预定义的文件夹布局。这意味着从 jar 创建捆绑包所需的全部操作就是向 JAR 清单添加一些条目。
OSGi 元数据由清单条目表示,这些条目指示 OSGi 框架捆绑包提供或/和需要什么。规范指示大约 20 个清单头,但我们只关注您最有可能使用的那些。
顾名思义,此标题指示导出哪些包(在捆绑包中可用),以便其他捆绑包可以导入它们。只有标题指定的包会被导出,其余的将是私有的,并且在包含捆绑包之外不可见。
类似于 Export-Package,此标题指示捆绑包导入的包。同样,只有此标题指定的包会被导入。默认情况下,导入的包是必需的 - 如果导入的包不可用,则导入捆绑包将无法启动。
下面是一个示例,摘自 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 术语中,根据可用性依赖于包转换为可选包导入。您已经在前面的示例中看到了这样的包
```code Import-Package: [...]edu.emory.mathcs.backport.java.util.concurrent;resolution:=optional ```由于在 OSGi 中,可以存在同一类的多个版本,因此最佳实践是在导出和导入包时都指定类包的版本。这是通过 version 属性完成的,该属性在每个包声明后添加。OSGi 支持的版本格式为 <major>.<minor>.<micro>.<qualifier>,其中 major、minor 和 micro 是数字,而 qualifier 是字母数字。
该 版本的含义 完全取决于捆绑包提供商,但是建议使用流行的编号方案,例如 Apache APR 项目 中的编号方案,其中
默认版本(如果属性不存在)为“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, ∞)"
作为最后一条提示,请确保在指定版本时始终使用引号,无论它是否是范围。
现在我们已经了解了一些有关捆绑包的信息,让我们看看可以使用哪些工具将现有 jar 文件转换为 osgi 格式
不建议使用这种自己动手的方法,因为拼写错误和多余的空格很容易潜入并使清单变得毫无用处。即使使用智能编辑器,清单格式本身也可能导致一些问题,因为它每行最多只能包含 72 个空格,如果超出限制,可能会导致一些难以理解的问题。手动创建或更新 jar 不是一个好主意,因为 jar 格式要求 META-INF/MANIFEST.MF 条目是存档中的第一个条目 - 如果不是,即使它存在于 jar 中,清单文件也不会被读取。手动方法确实推荐用于没有其他替代方案的情况。
但是,如果确实需要直接使用清单,则应使用可以处理 UNIX/DOS 空格的编辑器以及合适的 jar 创建实用程序(例如 JDK 附带的 jar 工具)来满足所有清单要求。
Bnd 代表 BuNDle 工具,是由 Peter Kriens(OSGi 技术负责人)创建的一个不错的实用程序,它“帮助 [...] 创建和诊断 OSGi R4 捆绑包”。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-Version Apache Ant 1.7.0
Created-By 1.5.0_07-87 ("Apple Computer, Inc.")
Extension-Name com.mchange.v2.c3p0
Implementation-Vendor Machinery For Change, Inc.
Implementation-Vendor-Id com.mchange
Implementation-Version 0.9.1.2
Manifest-Version 1.0
Specification-Vendor Machinery For Change, Inc.
Specification-Version 1.0
com.mchange.v2.c3p0.management com.mchange.v1.lang com.mchange.v2.c3p0
com.mchange.v2.c3p0.impl com.mchange.v2.debug
com.mchange.v2.log com.mchange.v2.management
java.sql
javax.management
javax.sql
指示指定在 jar 中发现的包(左侧)及其导入(右侧)。
One error 1 : Unresolved references to
[javax.management, javax.naming, javax.naming.spi, javax.sql, javax.xml.parsers, org.apache.log4j, org.w3c.dom]
by class(es) on the Bundle-Classpath[Jar:c3p0-0.9.1.2.jar]: [...]
。此部分很好地指示了给定 jar 导入的包。
让我们使用以下命令将工件转换为 OSGi 格式
java -jar bnd.jar wrap c3p0-0.9.1.2.jar
这将创建一个与原始 jar 内容完全相同的新存档,但 MANIFEST.MF 已修改,其中将包含标记为可选的 OSGi 导入。当前的 Bnd 工具使用 .jar$ 扩展名保存存档,而以前的版本则使用 .bar。
我们可以选择通过添加版本控制、排除某些导出的包以及将某些导入的包标记为必需(在本例中为 javax.sql)来调整 jar。为此,我们将创建一个 c3p0-0.9.1.2.bnd 文件,如下所示
version=0.9.1.2
Export-Package: com.mchange*;version=${version}
Import-Package: java.sql*,javax.sql*,*;resolution:=optional
Bundle-Version: ${version}
Bundle-Description: c3p0 connection pool
Bundle-Name: c3p0
请注意,对于版本,我们使用了变量替换。要挂接属性文件,请使用以下命令行
```code java -jar bnd.jar wrap -properties c3p0-0.9.1.2.bnd c3p0-0.9.1.2.jar ```
我使用了 .bnd 扩展名,因为默认情况下,Bnd ant 任务将在执行期间查找此文件。
要将 Bnd 工具与 ant 一起使用,只需导入开箱即用的任务并在 jar 创建期间调用它们即可
<taskdef resource="aQute/bnd/ant/taskdef.properties" classpath="${lib.dir}/bnd/bnd.jar"/>
...
<bndwrap definitions="${basedir}/osgi/bnd" output="${dist.dir}">
<fileset dir="${dist.dir}" includes="*.jar"/>
</bndwrap>
请注意,通常,后续会使用移动任务将 .jar$ 或 .bar 工件复制到原始 jar 上。
对于 Maven,Apache Felix Bundle 插件 提供了 Bnd 和 Maven 2 之间良好的集成。由于 Maven POM 包含有关项目的其他信息,因此 Bnd 插件可以使用项目属性自动填充清单的其他字段,例如 Bundle-License 或 Bundle-Version。
官方的 文档 详细解释了用法,因此我不会在此重复。
为了转换我们的 c3p0 库,我将使用一个简单的 Maven 2 pom,它将下载原始工件,然后将其包装为 bundle
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>my.company</groupId>
<artifactId>c3p0.osgi</artifactId>
<packaging>bundle</packaging>
<version>0.9.1.2-SNAPSHOT</version>
<name>c3p0.osgi</name>
<properties>
<export.packages>${export.package}*;version=${unpack.version}</export.packages>
<import.packages>*</import.packages>
<private.packages>!*</private.packages>
<symbolic.name>${pom.groupId}.${pom.artifactId}</symbolic.name>
<embed-dep>*;scope=provided;type=!pom;inline=true</embed-dep>
<unpack-bundle>false</unpack-bundle>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>1.2.0</version>
<configuration>
<unpackBundle>${unpack.bundle}</unpackBundle>
<instructions>
<Bundle-Name>${artifactId}</Bundle-Name>
<Bundle-SymbolicName>${symbolic.name}</Bundle-SymbolicName>
<Bundle-Description>${pom.name}</Bundle-Description>
<Import-Package>${import.packages}</Import-Package>
<Private-Package>${private.packages}</Private-Package>
<Include-Resource>${include.resources}</Include-Resource>
<Embed-Dependency>${embed-dep}</Embed-Dependency>
<_exportcontents>${export.packages}</_exportcontents>
</instructions>
</configuration>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
打包项目将创建一个 OSGi bundle,其内容与原始 bundle 相同,除了 MANIFEST.MF 将包含 OSGi 条目。
请注意使用属性来外部化插件配置。在模块内处理多个项目时,属性允许将通用插件配置放置在顶级 pom 中,并通过指定不同的属性值让每个子模块覆盖它。此类设置的一个实时 示例 是 Spring-DM osgi 存储库。
需要注意的是,Bnd 在创建 bundle 时会考虑类路径上所有可用的类。当从命令行使用 Bnd 时(如前面的示例),类路径仅由 jar 组成,因此除了 c3p0 之外不存在其他额外类。但是,当使用 Maven 或 Ant 等构建工具时,类路径会大得多——在这种情况下,根据您的导出/导入包指令,Bnd 可能会添加或丢弃来自结果 jar 的类。为了防止这种情况,请确保使用仅匹配实际包含的包的模式,即:com.mchange.* 而不是 *。
另一种方法(尽管不太可能遇到)是创建一个自定义工具,通常基于字节码分析。此类实用程序可以针对某些环境进行高度自定义,以提高速度或最大程度地减少内存占用,或支持其他启发式方法或配置文件。Spring Dynamic Modules 为其测试框架包含一个这样的内部 ASM 基于字节码的解析器,以便有效地创建即时 MANIFEST.MF。
但是,对于通用用途,Bnd 工具(无论是原始工具还是通过其 Maven 集成)提供了更多选项并且运行速度很快。事实上,使用越普遍,Bnd 通过其高度可定制性满足需求的可能性就越大。
话虽如此,在将现有库包装为 OSGi bundle 之前,请检查是否有人已经为您完成了此操作。您可以通过检查现有的 OSGi 存储库之一来做到这一点
OSGi Bundle Repository (ORB) - OSGi 联盟 bundle 存储库,提供“bundle 的联合集合”。
Eclipse Orbit - 包含可在 Eclipse 环境中使用的工件。由于 Eclipse 使用 Equinox,因此工件可能包含特定于 Equinox 的清单条目
Apache Felix Commons - 旨在“共享 [...] 捆绑的工件”
Apache OSGified 项目 - 一个简单的页面,指示 Apache Commons 项目已或即将在其官方发行版中包含 OSGi 清单条目。
希望通过社区的帮助,许多现有的流行 Java 库默认情况下将对 OSGi 友好,并且无需使用单独的存储库或包装 jar。在此之前,您可以通过为使用的项目提供补丁或简单地请求此功能来提供帮助。
在结束本文之前,我想邀请所有对 OSGi 和 Spring Dynamic Modules 感兴趣的人参加下周(2 月 27 日星期三)即将举行的 网络研讨会,该研讨会将涵盖核心 OSGi 概念和 Spring DM 基础知识。