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