Перейти к основному содержимому

Как разрешить конфликт версий артефактов в Maven

· 5 мин. чтения

1. Обзор

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

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

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

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

2. Версия Столкновение Артефактов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@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/foreach/version/collision/VersionCollisionUnitTest.java:[12,27] cannot find symbol
[ERROR] symbol: method immediateVoidFuture()
[ERROR] location: class com.google.common.util.concurrent.Futures

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

2.4. Использование плагина maven-зависимости

Плагин maven-dependency-plugin — очень полезный инструмент для представления всех зависимостей и их версий:

% mvn dependency:tree -Dverbose

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision ---
[INFO] com.foreach:project-collision:jar:0.0.1-SNAPSHOT
[INFO] +- com.foreach:project-a:jar:0.0.1-SNAPSHOT:compile
[INFO] | \- com.google.guava:guava:jar:22.0:compile
[INFO] \- com.foreach: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 project-collision :

<dependencies>
<dependency>
<groupId>com.foreach</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.foreach</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.foreach:project-collision:jar:0.0.1-SNAPSHOT
[INFO] \- com.foreach: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.foreach:project-collision:jar:0.0.1-SNAPSHOT
[INFO] +- com.foreach: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.foreach: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 предоставляет множество встроенных правил, упрощающих управление многомодульным проектом . Один из них запрещает использование классов и методов из транзитивных зависимостей .

Явное объявление зависимости устраняет возможность конфликта версий артефактов. Давайте добавим 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>

Как следствие, теперь мы должны явно объявить артефакт com.google.guava в нашем модуле Project-Collision , если мы хотим использовать его сами. Мы должны либо указать используемую версию, либо настроить dependencyManagement в родительском файле pom.xml . Это делает наш проект более защищенным от ошибок, но требует от нас большей ясности в наших файлах pom.xml .

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

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

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

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

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

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