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 .