java maven项目中的库版本冲突

Posted

技术标签:

【中文标题】java maven项目中的库版本冲突【英文标题】:Conflicting library version in a Java Maven project 【发布时间】:2013-11-08 16:17:55 【问题描述】:

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

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

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

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

【问题讨论】:

【参考方案1】:

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

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

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

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)

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

但是,在其他情况下,可能无法找到适用于所有人的依赖项版本。在这些情况下,您可能必须退回主要依赖项之一的版本,以使其使用适用于所有人的传递依赖项的版本。例如,在上面的示例中,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。

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

【讨论】:

看来我的情况是一个不能满足所有人的依赖。这主要是因为我将遗留软件(遗憾的是没有提供给我的源)与共享公共依赖项的较新库集成。所以总而言之,最好的解决方案是在编译时或之前得到错误和警告,以表明我需要做一些手动工作。至少它为我省去了部署然后找出冲突的麻烦。执行器插件似乎朝着这个方向工作得很好。 matts 的完美答案。对我来说,我留下了一个问题,为什么不同版本的多个罐子不能一起存在?为什么链接到 X 的 0.1 版的模块 a 不能与链接到 X 的 0.2 版的模块 b 一起使用?答案是 --> 因为类名:“加载到虚拟机中的每个类都由三件事唯一标识。它的名称、它的包和它的类加载器。”来自:kepler-project.org/developers/teams/framework/design-docs/….【参考方案2】:

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

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

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

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

希望这是有用的信息。

【讨论】:

【参考方案3】:

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

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

为所有具有project-c 传递依赖关系的依赖关系添加<exclusions/> 标签。

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

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

【讨论】:

【参考方案4】:

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

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

Dependency Convergence

<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>

【讨论】:

以上是关于java maven项目中的库版本冲突的主要内容,如果未能解决你的问题,请参考以下文章

Maven怎么处理引用的jar版本冲突

一点解决版本冲突的应急思路怎样在所有jar包文件中搜索冲突的方法?

Maven项目中的依赖出现版本冲突,最终发现是对Dependency Scope理解有误

idea 中解决maven 包冲突的问题(maven helper)

spring maven项目解决依赖jar包版本冲突方案

maven依赖冲突