解决Maven依赖收敛问题

47

我使用maven-enforcer-plugin检查依赖收敛问题,典型输出如下:

[WARNING] Rule 1: org.apache.maven.plugins.enforcer.DependencyConvergence failed 
  with message:
Failed while enforcing releasability the error(s) are [
Dependency convergence error for junit:junit:3.8.1 paths to dependency are:
+-foo:bar:1.0-SNAPSHOT
  +-ca.juliusdavies:not-yet-commons-ssl:0.3.9
    +-commons-httpclient:commons-httpclient:3.0
      +-junit:junit:3.8.1
and
+-foo:bar:1.0-SNAPSHOT
  +-junit:junit:4.11
]

看到这条信息,通常我会通过排除传递性依赖来“解决”它,例如:

<dependency>
  <groupId>ca.juliusdavies</groupId>
  <artifactId>not-yet-commons-ssl</artifactId>
  <version>0.3.9</version>
  <exclusions>
    <!-- This artifact links to another artifact which stupidly includes 
      junit in compile scope -->
    <exclusion>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </exclusion>
  </exclusions>
</dependency>

我想了解这是否真正是一个修复方法,并且以这种方式排除库所涉及的风险。据我所见:

  • “修复”通常是安全的,前提是我选择使用更新的版本。这依赖于库作者保持向后兼容性。

  • 通常不会影响Maven构建(因为近距离定义获胜),但是通过排除依赖项,我告诉Maven我知道这个问题,从而取悦了maven-enforcer-plugin。

我的想法是正确的吗?是否有其他处理此问题的方式?我对关注普遍情况的答案感兴趣-我意识到上面的junit示例有点奇怪。



3个回答

76
我们都认为JUnit永远不应该设置为除test之外的其他范围。一般来说,我认为除了排除不需要的依赖项之外,没有其他解决方案,所以我们都同意你这样做是正确的。
一个简单的例子:
正如Andreas Krueger所说,版本可能存在风险(我实际上遇到过)。假设项目的依赖关系如下:
+-foo:bar:1.0-SNAPSHOT
  +-group1:projectA:2.0
     +-group2:projectB:3.8.1
  +-group2:projectB:4.11

请注意,这只是对您情况的简化。看到这个依赖树,您将排除由projectA提供的依赖项projectB:
<dependency>
  <groupId>group1</groupId>
  <artifactId>projectA</artifactId>
  <version>2.0</version>
  <exclusions>
    <exclusion>
      <groupId>group2</groupId>
      <artifactId>projectB</artifactId>
    </exclusion>
  </exclusions>
</dependency>

使用Maven打包项目后,剩下的依赖项将是group2-someProjectB-4.11.jar,版本为4.11而不是3.8.1。一切都会很好,项目将在运行时没有遇到任何问题。

然后,一段时间后,假设您决定升级到项目A的下一个版本,即版本3.0,该版本增加了新的出色功能:

<dependency>
  <groupId>group1</groupId>
  <artifactId>projectA</artifactId>
  <version>3.0</version>
  <exclusions>
    <exclusion>
      <groupId>group2</groupId>
      <artifactId>projectB</artifactId>
    </exclusion>
  </exclusions>
</dependency>

问题在于,你还没有意识到,projectA版本3.0也已将其依赖项projectB升级到了5.0版本。
+-foo:bar:1.0-SNAPSHOT
  +-group1:projectA:3.0
     +-group2:projectB:5.0
  +-group2:projectB:4.11

在这种情况下,您之前排除的是projectB版本5.0。

然而,projectA版本3.0需要来自project B版本5.0的改进。由于排除的原因,在使用maven打包项目后,剩余的依赖项将是group2-someProjectB-4.11.jar,版本为4.11而不是5.0。一旦您使用projectA的新功能,程序将无法正确运行。

解决方案是什么?

我在一个Java-EE项目中遇到了这个问题。

一个团队开发了数据库服务,并将其打包为projectA。每次更新服务时,他们还会更新列出所有当前依赖项和当前版本的文件。

ProjectA是我正在工作的Java-EE项目的一个依赖项。每次服务团队更新ProjectA时,我也会检查版本更新。

实际上,排除依赖关系没有任何危害。但是,每次更新已设置排除的依赖项时,您必须检查:

  • 如果此排除仍然有意义。
  • 如果您需要升级自己项目中被排除的依赖项的版本。

我想Maven的排除依赖就像厨房刀一样。它很锋利,可以轻松地切割蔬菜,但在处理时需要小心...


1
非常好的回复,谢谢。我没有考虑到维护排除的危险。 - Duncan Jones
我授予您赏金,因为您的答案详细程度非常高。看到一个(有点)真实世界的例子说明了这个问题可能会出现的情况,感觉很好。 - Duncan Jones
@DuncanJones 谢谢。我的答案更像是一个警告而不是一个解决方案。但我仍然希望它能有所帮助。 - user2015707
在JUnit的编译范围特定情况下,排除法是正确的方法,但“一般来说”,依赖管理方法应该更可取。请参见http://timsteffens.blogspot.com/2014/05/solving-conflicts-with-transitive-maven.html。 - Pino
@DuncanJones:太棒了,电影《月球》:-) - Kieran Ryan

3
如果JUnit作为一个依赖项在编译范围内以构件的形式出现,那么这说明是你的某个库存在缺陷,例如:ca.juliusdavies。
JUnit 应该始终包含在测试范围内。因此,在成功构建后,它不会被打包到生成的 .jar、.war 或 .ear 文件中。
通常情况下,排除已经包含的依赖项没有任何问题,例如当库1和库2共享同一个依赖项时。
当然,唯一可能发生的问题是库1和库2包含相同依赖构件的不同版本。这可能导致运行时错误,因为库的功能已经发生了变化。 幸运的是,这种情况并不经常发生,除非版本号的差异很大。一般来说,推荐包含最新的依赖版本并排除旧版本。这在多数情况下是可行的。
如果不行,检查项目的一级依赖项是否有更新。

关于您的第一点,是的,您是正确的(因此在我的XML中有评论抱怨需要这样做的愚蠢)。听起来我们同意您的第二点(关于向后兼容性)。 - Duncan Jones
Duncan,我认为我已经回答了你的第二个问题。风险是可能面临运行时错误。由于直接依赖项已经编译,当您包含它们时,Maven构建将不会告诉您。当混合不同版本的间接依赖项时,可能会发生所有这些情况:运行时错误、WAR、JAR或EAR文件在Web或应用程序容器中的部署错误。 - Andreas Krueger

0

在项目中使用enforcer解决依赖问题的最佳方法是,在依赖管理中添加引起错误的依赖项,即pom.xml文件。例如,在这里junit引起了问题,因此选择更高版本并将其添加为pom.xml文件中的依赖项。

            <dependency><groupId>junit_or_group_id_error_causing_dependency</groupId><artifactId>Junit_or_artifact_id_group_id_error_causing_dependency</artifactId><version>4.11_or_higher_version_of_error_causing_dependency</version></dependency>

排除可以是其他解决方案,但我发现这是更清晰的执行相同操作的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接