44

在构建具有许多依赖项的 Maven 项目时,其中一些依赖项依赖于同一个库但使用不同的版本,这会在运行应用程序时导致错误。

例如,如果我添加了两个不同的项目依赖项 A 和 B,它们都依赖于 Apache Commons HTTP 客户端,但每个都依赖于不同的版本,一旦类加载器加载 A 的 Apache Commons http 客户端类,B 将尝试使用它们,因为它们已经由类加载器加载。

但是 B 的字节码取决于加载的类的不同版本,在运行应用程序时会导致多个问题。一个常见的异常是 method-not-found 异常(因为 A 的 http 客户端版本不再使用特定的方法)。

构建时避免此类冲突的一般策略是什么?是否必须手动检查依赖关系树才能确定哪些公共库相互冲突?

4

4 回答 4

40

您可以使用 Maven 依赖插件的tree目标来显示项目中的所有传递依赖项,并查找“因冲突而忽略”的依赖项。1

mvn dependency:tree -Dverbose
mvn dependency:tree -Dverbose | grep 'omitted for conflict'

一旦您知道哪个依赖项有版本冲突,您可以使用该includes参数仅显示导致该依赖项的依赖项,以查看特定依赖项是如何被拉入的。例如,一个项目,其中不同版本的 C 被 A 拉入,并且乙:

mvn dependency:tree -Dverbose -Dincludes=project-c

[INFO] com.my-company:my-project:jar:1.0-SNAPSHOT
[INFO] +- project-a:project-a:jar:0.1:compile
[INFO] |  \- project-c:project-c:jar:1.0:compile
[INFO] \- project-b:project-b:jar:0.2:compile
[INFO]    \- project-x:project-x:jar:0.1:compile
[INFO]       \- (project-c:project-c:jar:2.0:compile - omitted for conflict)

要真正解决冲突,在某些情况下,可能会找到两个主要依赖项都可以使用的传递依赖项的版本。将传递依赖项添加到dependencyManagement您的 pom 部分并尝试更改版本,直到一个工作。

但是,在其他情况下,可能无法找到适用于所有人的依赖项版本。在这些情况下,您可能必须退回主要依赖项之一的版本,以使其使用适用于所有人的传递依赖项的版本。例如,在上面的示例中,A 0.1 使用 C 1.0,B 0.2 使用 C 2.0。假设 C 1.0 和 2.0 完全不兼容。但也许您的项目可以改用 B 0.1,它恰好依赖于与 C 1.0 兼容的 C 1.5。

当然,这两种策略并不总是奏效,但我之前已经找到了成功。其他更激进的选择包括打包您自己版本的依赖项以修复不兼容性,或者尝试将两个依赖项隔离在单独的类加载器中。

于 2013-10-29T14:58:40.680 回答
28

欢迎来到Maven 依赖地狱,这是众所周知的。随着项目的增长和引入更多外部依赖项,这是一个比较常见的问题。

除了 Apache Commons(在您的原始问题中提到)之外,日志框架(log4j、slf4j)是另一个常见的罪魁祸首。

我同意“matts”给出的关于一旦发现冲突如何解决的建议。在及早捕获这些版本冲突方面,您还可以使用 maven "enforcer" 插件。请参阅“dependencyConvergence”配置。另请参阅此 SO 帖子

使用强制插件会在版本冲突时立即使构建失败,从而使您免于手动检查。这是一种激进的策略,但可以防止提示您提问/发帖的运行时问题类型。像任何东西一样,enforcer 插件有利有弊。我们在去年开始使用它,但后来发现它可能是一种祝福和诅咒。许多版本的库/框架都是向后兼容的,因此在编译时和运行时(无论是直接还是间接地)依赖于 1.2.3 和 1.2.4 版本通常都很好。但是,enforcer 插件将标记此冲突并要求您准确声明您想要的版本。假设依赖冲突的数量很少,这不需要太多工作。然而,一旦你引入了一个大型框架(例如 Spring MVC),它就会变得很糟糕。

希望这是有用的信息。

于 2013-10-31T06:45:24.853 回答
3

您可以在 pom 中使用 maven-enforcer-plugin 来强制传递依赖项的特定版本。这将帮助您防止出现冲突时 pom 配置的遗漏。

这对我有用,我能够更改版本以匹配。如果您无法更改版本,那么这将不是很有帮助。

依赖收敛

<project>
...
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>1.4</version>
        <executions>
          <execution>
            <id>enforce</id>
            <configuration>
              <rules>
                <dependencyConvergence/>
              </rules>
            </configuration>
            <goals>
              <goal>enforce</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
  </build>
  ...
</project>

使用方括号强制依赖版本:

<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <scope>compile</scope>
        <version>[1.0.0]</version>
</dependency>
于 2015-03-06T15:13:39.440 回答
1

我想扩展 Todd 和 Matts 的答案,因为您可以:

  • mvn dependency:tree -Dverbose -Dincludes=project-c

  • <exclusions/>为所有具有传递依赖项的依赖项添加一个标签project-c

  • 或者,或者,在您的项目中,明确定义project-c为依赖项,以覆盖传递的依赖项并避免冲突。(使用 `-Dverbose 时,这仍会显示在您的树中)。

或者,如果这些项目在您的控制之下,您可以简单地升级project-c.

于 2013-10-31T10:12:20.300 回答