在学习OSGi时,首先要了解的概念之一是bundle(包)的概念。在本篇博文中,我想更仔细地研究bundle究竟是什么,以及如何将一个普通的jar文件转换为OSGi bundle。所以,话不多说,
什么是bundle?
OSGi规范将bundle描述为一个“模块化单元”,它“由Java类和其他资源组成,这些资源共同为最终用户提供功能”。到目前为止还不错,但是bundle究竟是什么呢?再次引用规范:
bundle是一个JAR文件,它
- 包含[...]资源
- 包含一个描述JAR文件内容并提供关于bundle信息的清单文件
- 可以在JAR文件的OSGI-OPT目录或其子目录之一中包含可选文档
简而言之,bundle = jar + OSGi信息(在JAR清单文件中指定 - META-INF/MANIFEST.MF),不需要额外的文件或预定义的文件夹布局。这意味着从jar创建bundle只需要在JAR清单中添加一些条目。
OSGi元数据
OSGi元数据由清单条目表示,这些条目指示OSGi框架bundle提供什么或/和需要什么。规范指示大约20个清单头,但我们只看看你最有可能使用的那些。
Export-Package
顾名思义,此头指示导出哪些包(在bundle中可用),以便其他bundle可以导入它们。只有标题指定的包将被导出,其余的将是私有的,并且在包含bundle之外将不可见。
Import-Package
类似于Export-Package,此头指示bundle导入的包。同样,只有此标题指定的包将被导入。默认情况下,导入的包是必需的——如果导入的包不可用,则导入bundle将无法启动。
Bundle-SymbolicName
唯一必需的标题,此条目指定bundle的唯一标识符,基于反向域名约定(也由Java包使用)。
Bundle-Name
定义此bundle的可读名称,不包含空格。建议设置此标题,因为它可以提供比
Bundle-SymbolicName更短、更有意义的关于bundle内容的信息。
Bundle-Activator
该
BundleActivator是一个OSGi特定的接口,允许Java代码在OSGi框架启动或停止bundle时收到通知。此标题的值应包含激活器类的完全限定名称,该名称应是公共的,并且包含没有任何参数的公共构造函数。
Bundle-Classpath
当jar包含嵌入式库或位于不同文件夹下的类包时,此标题非常方便,方法是扩展默认的bundle类路径(该路径期望类直接在jar根目录下可用)。
Bundle-ManifestVersion
这个鲜为人知的标题指示用于读取此bundle的OSGi规范。
1表示OSGi 3版,而
2表示OSGi 4版及更高版本。由于
1是默认版本,因此强烈建议指定此标题,因为OSGi 4版bundle在OSGi 3版下将无法按预期工作。
下面是一个示例,摘自使用上述一些标题的Spring 2.5.x核心bundle清单
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不可见。
总而言之,如果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或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工具)来满足所有清单要求。
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…