理解 OSGi uses 指令

工程 | Glyn Normington | 2008 年 10 月 20 日 | ...

如果您为 SpringSource dm Server 或任何其他 OSGi 平台构建应用程序,您可能很快就会遇到 uses 指令。除非您对该指令的目的有清晰的了解,否则您将不知道何时编写它,并且当捆绑包因 uses 冲突而无法解析时,您将无从猜测。本文将让您彻底理解 uses 指令、何时使用它以及如何调试 uses 冲突。

捆绑包解析

OSGi 的设计旨在一旦捆绑包被“解析”,您通常不会遇到类转换异常和由于类型不匹配引起类似问题。这非常重要,因为 OSGi 为每个捆绑包使用一个类加载器,因此用户有很多机会遇到各种类型的类型不匹配。

理解任何 Java 类型(例如类或接口)在运行时可用之前都必须由类加载器加载,这一点至关重要。运行时类型由类型的完全限定类名和定义该类型的类加载器组合定义。如果同一个完全限定类名由两个不同的类加载器定义,那么就会产生两种不兼容的运行时类型。如果这两种类型发生接触,这种不兼容性会导致运行时错误。例如,尝试将其中一种类型转换为另一种类型将导致类转换异常。

OSGi 不是唯一基于类加载器的 Java 模块系统,但它是迄今为止最成熟的。这意味着 OSGi 的设计者已经对这类问题进行了长期深入的思考,并在 OSGi 规范中包含了解决方案。OSGi 的设计目标是在应用程序代码运行之前解决这些问题,这个过程称为解析。解析类似于强类型编程语言(如 Java)中的编译,因为某些类别的问题在应用程序代码开始运行之前就被诊断出来。让您的捆绑包解析有时可能会有点令人头疼,但这比诊断类转换异常等运行时错误要好得多。

那么解析捆绑包是什么意思呢?它意味着满足捆绑包的依赖关系,通常是通过找到导出给定捆绑包导入的包并且满足各种约束条件的捆绑包。最明显的约束是每个导出的包版本应位于包导入的包版本范围内。另一种约束是可以在包导入上指定任意属性,这些属性必须与相应的包导出的属性匹配。

由多个捆绑包导出的包

uses 约束(我们很快就会讨论到)旨在消除由一个包被多个捆绑包导出引起的类型不匹配问题。当需要一个来自另一个捆绑包的类型时,却使用了来自一个捆绑包的类型,就会发生类型不匹配,因为运行时类型不兼容。例如,尝试使用来自另一个捆绑包的具有相同类名的不同类型来转换来自一个捆绑包的类型时,就会发生类转换异常。这怎么会发生呢?由于一个捆绑包不能从多个捆绑包导入同一个包,因此必须有其他方式使冲突的类型接触。这通过一种类型被通过另一个包中的类型传递发生。

有两种方式可以将一种类型通过另一种类型传递。第一种方式是一种类型明确地引用另一种类型。例如,org.bar 包中的 Bar 类型的一个方法可能引用 org.foo 包中的 Foo 类型: public Foo getFoo();

另一种可以将类型通过另一种类型传递的方式是隐式地,通过超类型。例如,一个方法的签名可能引用一个超类型: public Object getFoo(); 在隐式情况下,超类型的一个实例会在某个时刻被转换为冲突的类型。

因此,这就是在 Java 代码层面发生类型不匹配的方式。让我们看看捆绑包清单文件是什么样的。

所需的类型 Foo 可能由导出 org.bar 包的同一捆绑包 (B) 导出: bundle-symbolicname: B bundle-manifestversion: 2 export-package: org.foo,org.bar 或由另一个捆绑包 (F) 导出: bundle-symbolicname: B bundle-manifestversion: 2 export-package: org.bar import-package: org.foo bundle-symbolicname: F bundle-manifestversion: 2 export-package: org.foo

引入 uses 指令是为了让 OSGi 能够在捆绑包解析期间诊断上述类型的类型不匹配问题。

uses 指令

为了在解析期间检测上述类型的潜在类型不匹配问题,在相应的捆绑包清单文件中声明了 Java 代码层面的显式或隐式类型引用。包含引用类型的包的导出被标记了一个 uses 指令,该指令声明了被引用类型的包。

在上述示例中,org.bar 包的导出被声明为 使用 org.foo 包: ... export-package: org.bar;uses:="org.foo" ... 请注意,uses 指令中命名的包要么由包含 uses 指令的捆绑包清单文件导出,要么由其导入。因此,以下清单文件是有效的: ... export-package: p;uses:="q,r", q import-package: r ... 而以下清单文件是无效的(因为它既不导出也不导入包 q): ... export-package: p;uses:="q,r" import-package: r ...

传递性 uses

类型引用是传递的。例如,如果类型 A 引用类型 B,而 B 又引用另一个类型 C,那么 A 的用户可以通过 B 获取对 C 的引用。

由于类型引用是传递的,OSGi 会自动考虑这一点。它形成了所谓的 uses 指令的传递闭包。这意味着为每个类型引用编写一个 uses 指令就足够了,OSGi 会处理传递性类型引用。

例如,尽管捆绑包清单文件: ... export-package: p;uses:="q,r",q;uses:="r" import-package: r ... 是正确的,但以下捆绑包清单文件足以捕获从包 pq、从 qr,以及(传递地)从 pr 的类型引用: ... export-package: p;uses:="q",q;uses:="r" import-package: r ...

诊断 uses 冲突

捆绑包解析过程旨在满足所有约束,因此只有当它无法以满足所有 uses 约束的方式满足依赖关系时,才会报告 uses 冲突。SpringSource dm Server 在这些情况下发布的诊断信息有助于确定问题所在。

让我们看一个编造的例子来理解这个原理。假设我们正在开发两个工具捆绑包 F 和 B,它们将从客户端捆绑包 C 调用。假设我们引入了 F 的第二个版本,并尝试在服务器上部署具有以下清单文件的捆绑包。 Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: F Bundle-Version: 1 Bundle-Name: F Bundle Export-Package: org.foo;version=1 Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: F Bundle-Version: 2 Bundle-Name: F Bundle Export-Package: org.foo;version=2 Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: B Bundle-Version: 1 Bundle-Name: B Bundle Export-Package: org.bar;uses:="org.foo" Import-Package: org.foo;version="[1,2)" Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: C Bundle-Version: 1.0.0 Bundle-Name: C Bundle Import-Package: org.bar,org.foo;version="[2,3)" 当我们尝试部署捆绑包 C 时,dm Server 发出以下日志消息: <SPDE0018E> Unable to install application from location 'file:/xxx/C.jar/'. Could not satisfy constraints for bundle 'C' at version '1.0.0'. Cannot resolve: C Resolver report: Bundle: C_1.0.0 - Uses Conflict: Import-Package: org.bar; version="0.0.0" Possible Supplier: B_1.0.0 - Export-Package: org.bar; version="0.0.0" Possible Conflicts: org.foo . 行: Bundle: C_1.0.0 - Uses Conflict: Import-Package: org.bar; version="0.0.0" 告诉我们存在与捆绑包 C 导入 org.bar 包相关的 uses 约束违例。换句话说,C 试图使用的 org.bar 的导出具有无法满足的 uses 约束。

行: Possible Supplier: B_1.0.0 - Export-Package: org.bar; version="0.0.0" 告诉我们正在考虑的是 org.bar 的哪个提供者。

行: Possible Conflicts: org.foo 告诉我们哪个包违反了 uses 约束。

回顾细节,我们知道 uses 冲突是由于捆绑包 C 导入的 org.foo 包版本与捆绑包 B 导入的版本不同。一定有什么原因导致使用了不同的版本,当我们仔细检查包导入时,我们意识到我们忘记升级 B 以使用 F 的最新版本。

在将 B 的清单文件更新为: Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: B Bundle-Version: 1 Bundle-Name: B Bundle Export-Package: org.bar;uses:="org.foo" Import-Package: org.foo;version="[2,3)" 后,我们现在可以成功部署捆绑包 C

诊断复杂的 uses 冲突

我们编造的 uses 冲突非常简单,足以让我们通过长时间查看各种捆绑包清单文件来找到问题。对于更复杂的 uses 冲突,例如 uses 指令中列出了许多可能冲突的包时,您可以使用 Equinox 控制台(telnet 到端口 2401)检查已成功安装的捆绑包(dm Server 会卸载任何无法成功部署的捆绑包),这样可能会更快地取得进展。

您可以通过发出以下 Equinox 控制台命令来获取已安装捆绑包的列表: osgi> ss 在我们编造的问题中,它显示类似以下内容: ... 82 ACTIVE F_1.0.0 84 ACTIVE F_2.0.0 85 ACTIVE B_1.0.0

要显示 B 的捆绑包清单文件,请发出命令: osgi> headers 85 要显示包 org.foo 的导入者和导出者,请发出命令: osgi> packages org.foo

总结

本文探讨了对 uses 指令的需求,它如何用于提供对某一类类型不匹配错误的早期诊断,以及如何使用 dm Server 诊断和 Equinox 控制台来追查 uses 约束违例的根本原因。

您可能认为 uses 指令带来的麻烦多于其价值,但当您考虑另一种情况,即在应用程序运行时追查可能难以察觉的类转换异常的原因时,您就会开始看到 uses 的基本原理。实际上,任何没有提供等效 uses 约束的 Java 模块系统,一旦您的应用程序模块有了多个版本,很可能就会在运行时出现类转换异常。OSGi uses 指令解决了 Java 模块化的一个普遍问题。

获取 Spring 时事通讯

订阅 Spring 时事通讯,保持联系

订阅

先行一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单订阅。

了解更多

即将到来的活动

查看 Spring 社区所有即将到来的活动。

查看全部