«1. Обзор

Многомодульные проекты Maven могут иметь сложные графы зависимостей. Это может привести к необычным результатам, чем больше модули импортируют друг друга.

В этом уроке мы увидим, как разрешить конфликт версий артефактов в Maven.

Мы начнем с многомодульного проекта, в котором намеренно использовали разные версии одного и того же артефакта. Затем мы увидим, как предотвратить получение неправильной версии артефакта с помощью исключения или управления зависимостями.

Наконец, мы попробуем использовать maven-enforcer-plugin, чтобы упростить управление, запретив использование транзитивных зависимостей.

2. Столкновение артефактов версий

Каждая зависимость, которую мы включаем в наш проект, может ссылаться на другие артефакты. Maven может автоматически добавлять эти артефакты, также называемые транзитивными зависимостями. Конфликт версий происходит, когда несколько зависимостей ссылаются на один и тот же артефакт, но используют разные версии.

В результате в наших приложениях могут быть ошибки как на этапе компиляции, так и во время выполнения.

2.1. Структура проекта

Давайте определим многомодульную структуру проекта для экспериментов. Наш проект состоит из родительского модуля с конфликтом версий и трех дочерних модулей:

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

Файлы pom.xml для проекта-a и проекта-b практически идентичны. Единственная разница — это версия артефакта com.google.guava, от которой они зависят. В частности, проект-а использует версию 22.0:

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

Но проект-б использует более новую версию, 29.0-jre:

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

Третий модуль, проект-коллизия, зависит от двух других:

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

Итак, какая версия гуавы будет доступна для коллизии проектов?

2.2. Использование функций из конкретной версии зависимости

Мы можем узнать, какая зависимость используется, создав простой тест в модуле проекта-коллизии, который использует метод Futures.immediateVoidFuture из гуавы:

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

Этот метод доступен только из версия 29.0-jre. Мы унаследовали это от одного из других модулей, но мы можем скомпилировать наш код, только если мы получили транзитивную зависимость от проекта-b.

2.3. Ошибка компиляции, вызванная конфликтом версий

В зависимости от порядка зависимостей в модуле 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

Это результат конфликта версий com.google. артефакт гуавы. По умолчанию для зависимостей одного уровня в дереве зависимостей Maven выбирает первую найденную библиотеку. В нашем случае обе зависимости com.google.guava имеют одинаковую высоту и выбрана более старая версия.

2.4. Использование maven-dependency-plugin

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)

Флаг -Dverbose отображает конфликтующие артефакты. На самом деле у нас есть зависимость com.google.guava в двух версиях: 22.0 и 29.0-jre. Именно последний мы хотели бы использовать в модуле проекта-коллизии.

3. Исключение транзитивной зависимости из артефакта

Одним из способов разрешения конфликта версий является удаление конфликтующей транзитивной зависимости из определенных артефактов. В нашем примере мы не хотим, чтобы библиотека com.google.guava транзитивно добавлялась из проекта-артефакта.

Таким образом, мы можем исключить его из проекта-коллизии 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>

Теперь, когда мы запустим команду 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

Как В результате фаза компиляции завершается без ошибок, и мы можем использовать классы и методы из версии 29.0-jre.

4. Использование раздела dependencyManagement

Раздел Maven dependencyManagement представляет собой механизм централизации информации о зависимостях. Одной из его наиболее полезных функций является управление версиями артефактов, используемых в качестве транзитивных зависимостей.

Имея это в виду, давайте создадим конфигурацию dependencyManagement в нашем родительском pom:

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

В результате Maven обязательно будет использовать артефакт com.google.guava версии 29.0-jre во всех дочерних модулях:

% 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. Предотвращение случайных транзитивных зависимостей

Плагин maven-enforcer предоставляет множество встроенных правил, которые упрощают управление многомодульным проектом. Один из них запрещает использование классов и методов из транзитивных зависимостей.

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

Явное объявление зависимостей устраняет возможность конфликта версий артефактов. Давайте добавим плагин maven-enforcer-plugin с этим правилом к ​​нашему родительскому pom: себя. Мы должны либо указать используемую версию, либо настроить dependencyManagement в родительском файле pom.xml. Это делает наш проект более защищенным от ошибок, но требует от нас большей ясности в наших файлах pom.xml.

6. Заключение

В этой статье мы рассмотрели, как разрешить конфликт версий артефактов в Maven.

Сначала мы рассмотрели пример конфликта версий в многомодульном проекте.

Затем мы показали, как исключить транзитивные зависимости в pom.xml. Мы рассмотрели, как управлять версиями зависимостей с помощью раздела dependencyManagement в родительском файле pom.xml.

Наконец, мы попробовали maven-enforcer-plugin запретить использование транзитивных зависимостей, чтобы заставить каждый модуль взять на себя управление своим собственным.

Как всегда, код, показанный в этой статье, доступен на GitHub.