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