SpringSource 应用平台清单头部

工程 | Glyn Normington | 2008年5月8日 | ...

SpringSource 应用平台由 OSGi Bundle 构建而成,并支持同样由 OSGi Bundle 构建的应用。该平台支持 OSGi 的标准特性,但也支持一些额外的清单头部。有几个人问过 为什么 SpringSource 添加了专有头部?新头部的语义是什么?,所以这篇博文解释了 Import-LibraryImport-Bundle 的背景动机和语义。

标准 OSGi Bundle 支持

该平台基于 OSGi R4.1 标准(或者如果你喜欢,也可以称作 JSR 291)构建,并使用 Equinox 作为其 OSGi 实现。因此,你可以使用平台的工具开发标准的 OSGi Bundle,并将这些 Bundle 部署到平台上,自平台发布以来,许多用户一直在这样做。

因此,精通 OSGi 的开发者可以将该平台用作标准的 OSGi 容器,并受益于平台特性,例如:

  • 能够使用管理控制台部署 Bundle,或将 Bundle 放置在平台的 pickup 目录中进行部署,
  • 诊断功能,如解析失败诊断、应用特定跟踪和自动死锁检测,
  • 与 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-LibraryImport-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,则如果找不到合适的库,该库导入将被忽略。

例如,以下头部导入了版本从 2.5 开始的某个版本的 Spring Framework 库,但如果没有合适的库可用,则会被忽略:

    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 保留其标准语义,包括合并拆分包的能力。但我们希望 Import-LibraryImport-Bundle 具有与 Import-Package 相同的底层语义,从而避免拆分包的复杂性。

我们还预计,随着平台的不断发展,我们将需要为 Import-LibraryImport-Bundle 添加更多指令,这些指令不适合添加到 Require-Bundle

接下来是什么?

平台 beta 项目正在进行中,我们将认真听取所有关于平台特性(包括新清单头部)的反馈。

对于希望利用平台头部,但又需要生成能在其他 OSGi 容器上运行的 Bundle 的用户,我们计划提供一个工具,将 Import-LibraryImport-Bundle 语法糖替换为等效的标准包导入。

我们还将与 OSGi Alliance 的同事讨论新的清单头部,以确定是否存在适合 OSGi 未来解决的通用需求。

订阅 Spring 简报

通过 Spring 简报保持联系

订阅

抢占先机

VMware 提供培训和认证,助你加速前进。

了解更多

获取支持

Tanzu Spring 通过一项简单的订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部