诊断 OSGi uses 冲突

工程 | Rob Harrop | 2008 年 11 月 22 日 | ...

在他的最近的博客文章中,Glyn 介绍了 OSGi 的“uses”指令。在本博客中,我想深入探讨 uses 约束违规的原因,并提供一些诊断应用程序中 uses 问题的技巧。

在大多数示例中,我将使用原生的 Equinox 而不是 dm Server。原因在于 uses 约束并非 dm Server 所特有,而是与所有 OSGi 用户相关。在本博客的最后,我将演示 dm Server 内置的一些智能约束失败诊断功能。

依赖约束不匹配

uses 违规的最常见原因是与一个或多个依赖约束不匹配。以以下三个清单为例:

Manifest-Version: 1.0
Bundle-Name: Spring Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: spring
Bundle-Version: 2.5.5
Export-Package: spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
Import-Package: eclipselink;version="[1.0, 2.0)"

Manifest-Version: 1.0
Bundle-Name: EclipseLink 1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: eclipselink
Bundle-Version: 1
Export-Package: eclipselink;version="1.0.0"

Manifest-Version: 1.0
Bundle-Name: EclipseLink 2 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: eclipselink
Bundle-Version: 2
Export-Package: eclipselink;version="2.0.0"

这里您可以看到一个 spring bundle 和两个 eclipselink bundles。显然,这些不是真正的 bundle。 spring bundle 导入了范围在 [1.0, 2.0)eclipselink 包。显然,只有 eclipselink_1 bundle 可以满足此约束。现在,考虑来自两个不同应用程序的这些清单:

Manifest-Version: 1.0
Bundle-Name: App1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app1
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="[1.0, 1.0]"

Manifest-Version: 1.0
Bundle-Name: App2 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app2
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="[2.0, 2.0]"

这里我们可以看到 app1 导入了范围在 [1.0, 1.0] 的 eclipselink,而 app2 导入了范围在 [2.0, 2.0]eclipselink。如果我将这些 bundle 安装到 Equinox 中,然后尝试启动应用程序 bundle,控制台会显示如下内容:

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
2       RESOLVED    spring_2.5.5
3       RESOLVED    eclipselink_1.0.0
4       RESOLVED    eclipselink_2.0.0
5       ACTIVE      app1_1.0.0
6       INSTALLED   app2_1.0.0

这里我们可以看到 springeclipselink bundle 都已解析。 app1 bundle 已经启动,但 app2 bundle 无法启动。要找出原因,我们可以使用 diag 命令:

osgi> diag app2
file:/Users/robharrop/dev/resdiag/uses/app2/bin [6]
  Package uses conflict: Import-Package: spring.orm.hibernate; version="0.0.0"

这里我们可以看到 app2 bundle 无法解析,因为它导入 spring.orm.hibernate 时存在包 uses 冲突。这意味着 app2spring.orm.hibernate 的导入无法满足,因为它的其他导入之一与一个 uses 约束冲突,而这个 uses 约束存在于那个可能提供 spring.orm.hibernate 的 bundle 上——在这种情况下,就是 spring bundle。

诊断此问题的第一步是找出 spring.orm.hibernate bundle 的可能供应者。从我们的用例中,我们知道唯一可能的供应者是 spring bundle,但如果您不知道供应者是谁,可以使用 packages 命令来查找:

osgi> packages spring.orm.hibernate
spring.orm.hibernate; version="2.5.5"<file:/Users/robharrop/dev/resdiag/uses/spring/bin [2]>
  file:/Users/robharrop/dev/resdiag/uses/app1/bin [5] imports

这告诉我们 spring.orm.hibernate 包由 bundle 2 导出。有了这个信息,我们可以找出 bundle 2spring.orm.hibernate 包的 uses 指令中列出了哪些包:

osgi> headers 2
Bundle headers:
 Bundle-ManifestVersion = 2
 Bundle-Name = Spring Bundle
 Bundle-SymbolicName = spring
 Bundle-Version = 2.5.5
 Export-Package = spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
 Import-Package = eclipselink;version="[1.0, 2.0)"
 Manifest-Version = 1.0

这里我们可以看到 uses 中唯一的包是 eclipselink 包,所以它一定是罪魁祸首。确实,我们可以看到 Spring bundle 要求 eclipselink 的范围在 [1.0, 2.0),而 app2 要求 eclipselink 的范围在 [2.0, 2.0]——这两个范围互不相交,这意味着 app2 不能连接到与 spring bundle 相同版本的 eclipselink

uses 列表很长的情况下,您可以通过找出列出的包中哪些拥有多个供应者来缩小可能违规的范围。要出现 uses 约束违规,必须始终存在多个供应者。

版本不匹配并不是依赖约束不匹配的唯一原因。约束可能因为属性以及版本而不匹配。

安装顺序问题

如果我们重新回顾前面的例子,并修改 spring bundle 的清单,使其可以接受 2.0 版本的 eclipselink 包,并放宽 app1 的范围,使其可以接受任何高于 1.0 的版本,我们就应该能够解决问题。

Manifest-Version: 1.0
Bundle-Name: Spring Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: spring
Bundle-Version: 2.5.5
Export-Package: spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
Import-Package: eclipselink;version="[1.0, 2.0]"

Manifest-Version: 1.0
Bundle-Name: App1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app1
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="1.0"

安装 bundle 并启动应用程序 bundle 表明这一更改带来了很大的不同

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1       RESOLVED    spring_2.5.5
2       RESOLVED    eclipselink_1.0.0
3       RESOLVED    eclipselink_2.0.0
4       ACTIVE      app1_1.0.0
5       ACTIVE      app2_1.0.0

现在两个应用程序 bundle 都可以启动了。不幸的是,还有一个更隐蔽的问题等着我们。取决于安装顺序,这组 bundle 可能仍然无法一起运行。为了说明这一点,让我们一次性安装 springeclipselink_1app1,并启动 app1

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1       RESOLVED    spring_2.5.5
2       RESOLVED    eclipselink_1.0.0
3       ACTIVE      app1_1.0.0

现在,让我们安装 eclipselink_2app2

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1       RESOLVED    spring_2.5.5
2       RESOLVED    eclipselink_1.0.0
3       ACTIVE      app1_1.0.0
4       RESOLVED    eclipselink_2.0.0
5       INSTALLED   app2_1.0.0

app2 bundle 无法启动。来自 diag 的输出告诉了我们原因:

osgi> diag app2
file:/Users/robharrop/dev/resdiag/uses/app2/bin [5]
  Package uses conflict: Import-Package: spring.orm.hibernate; version="0.0.0"

uses 约束又回来了。按照上一节中确定的诊断步骤操作在这里无济于事,因为不存在依赖约束不匹配——我们知道这一点,因为第一次这些 bundle 解析得很好。

这里的问题在于解析顺序。这些 bundle 是分两个不同的批次安装和解析的。第一个批次包括 springeclipselink_1app1,第二个批次包括 eclipselink_2app2。当第一个批次被解析(由于启动 app1 bundle)时,spring bundle 在导入 eclipselink 包时被连接到 eclipselink_1 bundle。这可以通过控制台确认:

osgi> bundle app1
file:/Users/robharrop/dev/resdiag/uses/app1/bin [3]
  Id=3, Status=ACTIVE      Data Root=/opt/springsource-dm-server-1.0.0.RELEASE/lib/configuration/org.eclipse.osgi/bundles/3/data
  No registered services.
  No services in use.
  No exported packages
  Imported packages
    spring.orm.hibernate; version="2.5.5"<file:/Users/robharrop/dev/resdiag/uses/spring/bin [1]>
    eclipselink; version="1.0.0"<file:/Users/robharrop/dev/resdiag/uses/eclipselink1/bin [2]>
  No fragment bundles
  Named class space
    app1; bundle-version="1.0.0"[provided]
  No required bundles

注意,导入的包部分显示 eclipselink 版本 1.0.0 是从 eclipselink_1 bundle 导入的。当第二个批次被安装时,app2 bundle 无法解析,因为它需要 eclipselink 的版本 2.0.0,但 spring 已经连接到 eclipselink 的版本 1.0.0。当所有 bundle 作为一次性批次安装和解析时,OSGi 解析器将尝试满足所有约束,包括确保 spring.orm.hibernate 上的 uses 约束可以得到满足。

要解决这个问题,我们不需要修改我们的 bundle。相反,我们可以将 bundle 分一次性安装,或者触发针对 spring bundle 的刷新——这实际上是要求 OSGi 重新运行解析过程。现在 eclipselink_2 bundle 已安装,我们可以预期这将产生不同的结果:

osgi> refresh spring

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1       RESOLVED    spring_2.5.5
2       RESOLVED    eclipselink_1.0.0
3       ACTIVE      app1_1.0.0
4       RESOLVED    eclipselink_2.0.0
5       ACTIVE      app2_1.0.0

osgi> bundle spring
file:/Users/robharrop/dev/resdiag/uses/spring/bin [1]
  Id=1, Status=RESOLVED    Data Root=/opt/springsource-dm-server-1.0.0.RELEASE/lib/configuration/org.eclipse.osgi/bundles/1/data
  No registered services.
  No services in use.
  Exported packages
    spring.orm.hibernate; version="2.5.5"[exported]
  Imported packages
    eclipselink; version="2.0.0"<file:/Users/robharrop/dev/resdiag/uses/eclipselink2/bin [4]>
  No fragment bundles
  Named class space
    spring; bundle-version="2.5.5"[provided]
  No required bundles

注意,刷新 spring 使得 app2 bundle 得以解析。 springeclipselink 包的连接已更改为由来自 eclipselink_2 bundle 的版本 2.0.0 导出所满足。

dm Server 中的 Uses 约束

当您在 dm Server 中遇到 uses 约束违规时,我们已经尝试为您执行一些分析步骤,特别是识别可能不匹配的依赖约束。

Could not satisfy constraints for bundle 'app2' at version '1.0.0'.
 Cannot resolve: app2
  Resolver report:
    Bundle: app2_1.0.0 - Uses Conflict: Import-Package: spring.orm.hibernate; version="0.0.0"
      Possible Supplier: spring_2.5.5 - Export-Package: spring.orm.hibernate; version="2.5.5"
        Possible Conflicts: eclipselink

Uses 约束在企业级库中很常见,手动诊断故障可能是一场噩梦。特别是,当您导出的包在其 uses 子句中列有 10 个或更多包时,确定可能的冲突可能会非常耗时。因此,自动化诊断是必不可少的,我希望不断改进 dm Server 中的诊断代码,使处理常见错误变得微不足道。

在下一个版本中,我们计划将诊断工具直接构建到我们的 dm Server Eclipse 工具中,以便大多数这些问题都能由 dm Server 自动诊断。

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助您快速提升。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅即可获得 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部