SpringSource 应用平台由 OSGi bundle 构建而成,并支持同样由 OSGi bundle 构建的应用程序。该平台支持 OSGi 的标准功能,但也支持一些额外的清单头。有些人问过 为什么 SpringSource 添加了专有头?
以及 新头的语义是什么?
,因此本文解释了背景动机以及 Import-Library 和 Import-Bundle 的语义。
标准 OSGi Bundle 支持
该平台基于
OSGi R4.1 标准构建,如果您愿意,也可以说是
JSR 291,并使用
Equinox 作为其 OSGi 实现。因此,您可以使用平台的工具开发标准 OSGi bundle,并将这些 bundle 部署到平台上,许多用户自平台发布以来一直在这样做。
因此,熟悉 OSGi 的开发者可以将平台用作标准的 OSGi 容器,并受益于平台的功能,例如
- 能够使用 Admin Console 或通过将 bundle 放入平台的 pickup 目录来部署 bundle,
- 诊断功能,例如解析失败诊断、应用程序特定跟踪和自动死锁检测,
- 与 Spring 和 Spring Dynamic Modules 的强大集成,适合希望使用这些框架的开发者,以及
- 从仓库自动提供依赖项。
然而,该平台还旨在让对 OSGi 几乎或完全没有接触的企业应用开发者也能轻松受益于 OSGi,这对平台提出了一些额外要求。
企业应用程序的额外要求
正如 Sam 最近关于平台部署选项的
博客所解释的,您可以在平台上部署现有的单体 WAR 文件,无需理解 OSGi - 平台会为您处理一切。但要受益于共享库、共享服务,以及最终的 PAR 文件作用域,需要将单体 WAR 文件分解为 OSGi bundle。这有多难?
嗯,过程中的一些步骤相对容易,特别是如果遵循了良好的软件工程实践并将代码组织成了服务、领域和基础设施组件。这些组件可以转换为 bundle,并且它们之间的依赖关系可以使用 META-INF/MANIFEST.MF 中的标准 OSGi Import-Package 和 Export-Package 头来表达。
更困难的一步是表达对企业框架(如 Spring 和 Hibernate)的依赖。完全可以使用标准的 OSGi Import-Package 和 Require-Bundle 头来表达这些依赖关系,如果您的目标是创建可以在其他 OSGi 容器中运行的 OSGi bundle,这正是您应该做的,但这种方法有一些隐性成本。
首先,开发者必须精确决定一个给定框架包含哪些包。仅仅导入应用程序代码使用的包是不够的,因为一些企业框架在应用程序加载时会将进一步的依赖项织入到应用程序的字节码中。开发者必须(很可能通过反复试验)发现需要导入哪些额外的实现包,以确保织入的应用程序行为正确。
然后是框架版本迁移的麻烦,因为组成框架的精确包集合可能已经改变。织入所需的额外包通常不是由公共契约定义的,因此可能会发生变化。
此外,由此产生的包导入未能正确捕获设计意图,这使得将来维护或扩展应用程序更加困难。
我们真的不想将这些负担强加给用户,因此我们创建了一些 SpringSource 应用平台特定的额外清单头,即 Import-Library 和 Import-Bundle,作为表达对企业框架依赖关系的便捷方式。正如您将在下面看到的,这些头实际上只是用标准 OSGi 包导入来表达的 语法糖
。
Import-Library
基本语法与其他清单头类似
Import-Library: <librarySymbolicName>;version=<versionRange>
其中
<librarySymbolicName> 是库的
符号名
,而
<versionRange> 是使用 OSGi 版本范围表示法表示的库的可接受版本范围。库定义指定了库的符号名和版本,这两者共同唯一地标识了平台中的库。
如果您不熟悉 OSGi 版本范围表示法,目前最常用的形式是最低版本范围,例如 2
,表示 版本 2 或更高
,以及 半开
范围,例如 [2.2.1,2.2.2)
,表示版本在 2.2.1(包含)到 2.2.2(不包含)之间。如果省略 version=<versionRange> 以及分号分隔符,则默认范围包含所有版本。
对于每个库导入,平台会选择具有给定符号名和在给定版本范围内平台仓库中可用的最高版本的库。然后,平台将库导入替换为一组与库 bundle 导出的所有包匹配的包导入。平台会检测到当一个 bundle 导入两个或多个导出相同包的库的情况,发出适当的日志消息,并无法安装该导入 bundle。
例如,以下头导入了版本在 2.5.4(包含)到 2.5.5(不包含)之间的 Spring Framework 库
Import-Library: org.springframework.spring;version="[2.5.4,2.5.5)"
可选库导入
您可以使用以下语法指示库导入是可选的。请注意特殊分隔符
:=
,它表示修改清单头语义的
指令
,与表示
匹配属性
(如
version)的分隔符
=
不同。
Import-Library: <librarySymbolicName>;version=<versionRange>;resolution:=optional
如果未指定 resolution,或指定为 mandatory,则如果不存在具有给定符号名和给定范围内版本的库,则包含该导入库头的 bundle 将无法安装。但如果指定了 resolution:=optional,则在没有合适的库可用时,该库导入将被忽略。
因此,例如,以下头导入 Spring Framework 库 2.5 或更高版本,但在没有合适的库可用时将被忽略
Import-Library: org.springframework.spring;version="2.5";resolution:=optional
导入多个库
如果您需要导入多个库,则在单个
Import-Library 清单头中指定一个逗号分隔的库导入列表,如下例所示
Import-Library: org.foo.p;version="[1,2)",org.bar.q;version="[2,3)"
Import-Bundle
Import-Bundle 是另一种便捷方式,适用于库只包含一个 bundle 且创建库定义不方便的情况。其语法与
Import-Library 非常相似,只是它引用的是 bundle 的符号名和版本,而不是库的。
正如您所预期的,对于每个导入的 bundle,平台会选择具有给定符号名和在给定版本范围内平台仓库中可用的最高版本的 bundle。然后,平台将 bundle 导入替换为一组与该 bundle 导出的包匹配的包导入。
因此,例如,以下头导入了 Hibernate 对象关系映射器 bundle
Import-Bundle: com.springsource.org.hibernate;version="[3.2.6,3.2.7)"
为什么不重载 Require-Bundle?
如果您熟悉 OSGi,您可能会问自己为什么我们没有重载
Require-Bundle,而是引入了
Import-Bundle。
嗯,我们希望 Require-Bundle 保留其标准语义,包括合并拆分包(split package)各个部分的能力。但我们希望 Import-Library 和 Import-Bundle 具有与 Import-Package 相同的底层语义,以避免拆分包带来的复杂性。
我们还预计,随着平台的不断发展,我们需要在 Import-Library 和 Import-Bundle 中添加更多指令,而这些指令不适合添加到 Require-Bundle 中。
下一步是什么?
平台
beta 项目 正在进行中,我们将听取所有关于平台功能的反馈,包括新的清单头。
对于希望利用平台头部,但需要生成能在其他 OSGi 容器上运行的 bundle 的用户,我们计划开发一个工具来替换 Import-Library 和 Import-Bundle...