How to Resolve a Version Collision of Artifacts in Maven – 如何解决Maven中工件的版本冲突问题

最后修改: 2020年 7月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Multi-module Maven projects can have complex dependency graphs. These can have unusual results, the more the modules import from each other.

多模块Maven项目可能有复杂的依赖关系图。模块之间的相互导入越多,就会产生不寻常的结果。

In this tutorial, we’ll see how to resolve version collision of artifacts in Maven.

在本教程中,我们将看到如何解决Maven中工件的版本碰撞问题

We’ll start with a multi-module project where we’ve deliberately used different versions of the same artifact. Then, we’ll see how to prevent getting the wrong version of an artifact with either exclusion or dependency management.

我们将从一个multi-module项目开始,在这个项目中我们故意使用同一个工件的不同版本。然后,我们将看到如何通过排除法或依赖性管理来防止获得错误的工件版本。

Finally, we’ll try using the maven-enforcer-plugin to make things easier to control, by banning the use of transitive dependencies.

最后,我们将尝试使用maven-enforcer-plugin,通过禁止使用反式依赖来使事情更容易控制。

2. Version Collision of Artifacts

2.版本碰撞的人工制品

Each dependency that we include in our project might link to other artifacts. Maven can automatically bring in these artifacts, also called transitive dependencies. Version collision happens when multiple dependencies link to the same artifact, but use different versions.

我们在项目中包含的每个依赖项都可能与其他工件相联系。Maven可以自动引入这些工件,也叫跨度依赖。当多个依赖项链接到同一个工件,但使用不同的版本时,就会发生版本冲突。

As a result, there may be errors in our applications both in the compilation phase and also at runtime.

因此,我们的应用程序中可能会出现错误在编译阶段和运行时也是如此

2.1. Project Structure

2.1.项目结构

Let’s define a multi-module project structure to experiment with. Our project consists of a version-collision parent and three children modules:

让我们定义一个多模块项目结构来进行实验。我们的项目由一个version-collision父模块和三个子模块组成。

version-collision
    project-a
    project-b
    project-collision

The pom.xml for project-a and project-b are almost identical. The only difference is the version of the com.google.guava artifact that they depend on. In particular, project-a uses version 22.0:

pom.xml对于project-aproject-b几乎是相同的。唯一的区别是它们所依赖的 com.google.guava artifact的版本。特别是,project-a使用的是22.0版本。

<dependencies>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>22.0</version>
    </dependency>
</dependencies>

But, project-b uses the newer version, 29.0-jre:

但是,项目-b使用较新的版本,29.0-jre

<dependencies>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>29.0-jre</version>
    </dependency>
</dependencies>

The third module, project-collision, depends on the other two:

第三个模块,project-collision,依赖于其他两个。

<dependencies>
    <dependency>
        <groupId>com.baeldung</groupId>
        <artifactId>project-a</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.baeldung</groupId>
        <artifactId>project-b</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

So, which version of guava will be available to project-collision?

那么,哪个版本的guava将提供给project-collision

2.2. Using Features from Specific Dependency Version

2.2.使用特定依赖版本的功能

We can find out which dependency is used by creating a simple test in the project-collision module that uses the Futures.immediateVoidFuture method from guava:

我们可以通过在project-collision模块中创建一个简单的测试,使用Futures.immediateVoidFuture方法从guava中找出哪个依赖性。

@Test
public void whenVersionCollisionDoesNotExist_thenShouldCompile() {
    assertThat(Futures.immediateVoidFuture(), notNullValue());
}

This method is only available from the 29.0-jre version. We’ve inherited this from one of the other modules, but we can only compile our code if we got the transitive dependency from project-b.

这个方法只在29.0-jre版本中可用。我们从其他模块中继承了这一点,但只有当我们从project-b.中获得横向依赖时,我们才能编译我们的代码。

2.3. Compilation Error Caused by Version Collision

2.3.版本碰撞导致的编译错误

Depending on the order of dependencies in the project-collision module, in certain combinations Maven returns a compilation error:

根据project-collision模块中的依赖顺序,在某些组合中,Maven会返回一个编译错误。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-testCompile) on project project-collision: Compilation failure
[ERROR] /tutorials/maven-all/version-collision/project-collision/src/test/java/com/baeldung/version/collision/VersionCollisionUnitTest.java:[12,27] cannot find symbol
[ERROR]   symbol:   method immediateVoidFuture()
[ERROR]   location: class com.google.common.util.concurrent.Futures

That’s the result of the version collision of the com.google.guava artifact. By default, for dependencies at the same level in a dependency tree, Maven chooses the first library it finds. In our case, both com.google.guava dependencies are at the same height and the older version is chosen.

这是com.google.guava工件的版本碰撞的结果。默认情况下,对于依赖树中处于同一级别的依赖项,Maven会选择它发现的第一个库。在我们的例子中,两个com.google.guava依赖项都在同一高度,因此选择了较早的版本。

2.4. Using maven-dependency-plugin

2.4.使用maven-dependency-plugin

The maven-dependency-plugin is a very helpful tool to present all dependencies and their versions:

maven-dependency-plugin是一个非常有用的工具,可以展示所有的依赖性及其版本。

% mvn dependency:tree -Dverbose

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision ---
[INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT
[INFO] +- com.baeldung:project-a:jar:0.0.1-SNAPSHOT:compile
[INFO] |  \- com.google.guava:guava:jar:22.0:compile
[INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- (com.google.guava:guava:jar:29.0-jre:compile - omitted for conflict with 22.0)

The -Dverbose flag displays conflicting artifacts. In fact, we have a com.google.guava dependency in two versions: 22.0 and 29.0-jre. The latter is the one we would like to use in the project-collision module.

-Dverbose标志显示冲突的工件。事实上,我们在两个版本中有一个com.google.guava依赖。22.0和29.0-jre。后者是我们想在project-collision模块中使用的那个。

3. Excluding a Transitive Dependency From an Artifact

3.从工件中排除一个过渡性的依赖关系

One way to resolve a version collision is by removing a conflicting transitive dependency from specific artifacts. In our example, we don’t want to have the com.google.guava library transitively added from the project-a artifact.

解决版本冲突的一种方法是通过从特定的工件中移除冲突的横向依赖关系。在我们的例子中,我们不想让com.google.guavalibrary从project-aartifact中过渡添加。

Therefore, we can exclude it in the project-collision pom:

因此,我们可以在project-collision pom中排除它。

<dependencies>
    <dependency>
        <groupId>com.baeldung</groupId>
        <artifactId>project-a</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <exclusions>
            <exclusion>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.baeldung</groupId>
        <artifactId>project-b</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

Now, when we run the dependency:tree command, we can see that it’s not there anymore:

现在,当我们运行dependency:tree命令时,我们可以看到它不在那里了。

% mvn dependency:tree -Dverbose

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision ---
[INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT
[INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- com.google.guava:guava:jar:29.0-jre:compile

As a result, the compilation phase ends without an error and we can use the classes and methods from version 29.0-jre.

结果,编译阶段结束时没有出现错误,我们可以使用29.0-jre版本的类和方法。

4. Using the dependencyManagement Section

4.使用dependencyManagement部分

Maven’s dependencyManagement section is a mechanism for centralizing dependency information. One of its most useful features is to control versions of artifacts used as transitive dependencies.

Maven的dependencyManagement部分是一个集中依赖信息的机制。它最有用的功能之一是控制作为横向依赖的工件的版本。

With that in mind, let’s create a dependencyManagement configuration in our parent pom:

考虑到这一点,让我们在我们的父pom中创建一个dependencyManagement配置。

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
         <version>29.0-jre</version>
      </dependency>
   </dependencies>
</dependencyManagement>

As a result, Maven will make sure to use version 29.0-jre of com.google.guava artifact in all child modules:

因此,Maven会确保在所有子模块中使用版本29.0-jrecom.google.guava神器。

% mvn dependency:tree -Dverbose

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision ---
[INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT
[INFO] +- com.baeldung:project-a:jar:0.0.1-SNAPSHOT:compile
[INFO] |  \- com.google.guava:guava:jar:29.0-jre:compile (version managed from 22.0)
[INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- (com.google.guava:guava:jar:29.0-jre:compile - version managed from 22.0; omitted for duplicate)

5. Prevent Accidental Transitive Dependencies

5.防止意外的过渡性依赖

The maven-enforcer-plugin provides many built-in rules that simplify the management of a multi-module project. One of them bans the use of classes and methods from transitive dependencies.

maven-enforcer-plugin提供了许多内置规则,简化了多模块项目的管理。其中一条禁止使用来自反式依赖关系的类和方法

Explicit dependency declaration removes the possibility of version collision of artifacts. Let’s add the maven-enforcer-plugin with that rule to our parent pom:

明确的依赖性声明消除了工件版本碰撞的可能性。让我们把带有该规则的maven-enforcer-plugin加入我们的父pom。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.0.0-M3</version>
    <executions>
        <execution>
            <id>enforce-banned-dependencies</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <banTransitiveDependencies/>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

As a consequence, we must now explicitly declare the com.google.guava artifact in our project-collision module if we want to use it ourselves. We must either specify the version to use, or set up dependencyManagement in the parent pom.xml. This makes our project more mistake proof, but requires us to be more explicit in our pom.xml files.

因此,如果我们想自己使用com.google.guavaartifact,我们现在必须在project-collision模块中明确声明它。我们必须指定要使用的版本,或者在父pom.xml中设置dependencyManagement这使我们的项目更加防错,但要求我们在pom.xml文件中更加明确。

6. Conclusion

6.结语

In this article, we’ve seen how to resolve a version collision of artifacts in Maven.

在本文中,我们看到了如何解决Maven中工件的版本冲突。

First, we explored an example of a version collision in a multi-module project.

首先,我们探讨了一个多模块项目中的版本碰撞的例子。

Then, we showed how to exclude transitive dependencies in the pom.xml. We looked at how to control dependencies versions with the dependencyManagement section in the parent pom.xml.

然后,我们展示了如何在pom.xml中排除过渡性依赖。我们研究了如何通过父级pom.xml中的dependencyManagement部分来控制依赖版本。

Finally, we tried the maven-enforcer-plugin to ban the use of transitive dependencies in order to force each module to take control of its own.

最后,我们尝试使用maven-enforcer-plugin来禁止使用横向依赖关系,以迫使每个模块都能控制自己。

As always, the code shown in this article is available over on GitHub.

一如既往,本文所示的代码可在GitHub上获得over。