领先一步
VMware 提供培训和认证,助您加速进步。
了解更多如果您为SpringSource dm Server或任何其他OSGi平台构建应用程序,您迟早会遇到“uses”指令。除非您清楚地理解该指令的目的,否则您将不知道何时编写它,并且当某个bundle由于“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` 包的导出被声明为“uses”`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 发出以下日志消息:“`
该行:“`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”指令的麻烦大于它的价值,但考虑到跟踪应用程序运行时中可能晦涩难懂的类转型异常原因的替代方案,您应该开始理解“uses”的原理。实际上,任何没有提供等同于“uses”约束的 Java 模块系统,一旦您编写了多个版本的应用程序模块,很可能会在运行时出现类转型异常。OSGi“uses”指令解决了 Java 模块化中的一个普遍问题。