领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多如果您为 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 能够在捆绑包解析期间诊断上述类型的类型不匹配。
为了在解析期间检测上述类型的潜在类型不匹配,在相应的捆绑包清单中声明了 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 ...
类型引用是传递的。例如,如果类型A 引用类型B,而类型B 又引用另一个类型C,则A 的用户可以通过B 获取对C 的引用。
由于类型引用是传递的,因此 OSGi 会自动考虑这一点。它形成了所谓的“uses”指令的“传递闭包”。这意味着为每个类型引用编码“uses”指令就足够了,OSGi 将处理传递类型引用。
例如,尽管捆绑包清单: ... export-package: p;uses:="q,r",q;uses:="r" import-package: r ... 是正确的,但以下捆绑包清单足以捕获从p 包到q 包的类型引用,从q 包到r 包的类型引用,以及(传递地)从p 包到r 包的类型引用: ... export-package: p;uses:="q",q;uses:="r" import-package: r ...
捆绑包解析过程旨在满足所有约束,因此只有在无法以满足所有“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”指令中列出了许多可能冲突的包时,您可能可以通过使用 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 模块化的一般问题。