Какие знаешь способы разрешения конфликта между двумя стартерами, использующими разные версии Tomcat?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разрешение конфликтов версий Tomcat в Spring Boot стартерах
Это частая проблема в Java проектах при использовании множества зависимостей. Spring Boot Starter for Web по умолчанию использует определенную версию Tomcat, но могут быть конфликты при использовании других стартеров или явном указании другой версии.
Понимание проблемы
Часто возникает ситуация:
spring-boot-starter-web (версия A) -> Tomcat 10.0.x
spring-boot-starter-webflux (версия B) -> Tomcat 9.0.x
// Какой Tomcat будет использован?
Maven/Gradle разрешают конфликты по правилу: выбирается версия, которая ближайшая в дереве зависимостей (обычно первая найденная).
Способ 1: Явное указание версии в dependencyManagement
Это самый рекомендуемый и чистый способ.
<!-- pom.xml -->
<project>
<properties>
<tomcat.version>10.1.5</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Явно указываем версию Tomcat для всех зависимостей -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-bom</artifactId>
<version>${tomcat.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Версия Tomcat возьмется из dependencyManagement -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<!-- Версия Tomcat все равно будет из dependencyManagement -->
</dependency>
</dependencies>
</project>
Плюсы:
- Единая точка контроля версий
- Гарантирует одну версию Tomcat
- Работает для транзитивных зависимостей
Минусы:
- Требует явного управления
- Нужно следить за совместимостью
Способ 2: Исключение конфликтующей зависимости (Exclusions)
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<!-- Исключаем конфликтующий Tomcat из этого стартера -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Потом явно добавляем нужную версию -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-core</artifactId>
<version>10.1.5</version>
</dependency>
Плюсы:
- Явный контроль
- Видно какие зависимости исключены
Минусы:
- Много boilerplate кода
- Сложно при множества конфликтов
- Хрупко при обновлении версий
Способ 3: Использование Spring Boot Parent POM
Самый простой способ - использовать Spring Boot BOM (Bill of Materials).
<!-- pom.xml -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version> <!-- Явно указываем версию Boot -->
</parent>
<project>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Версия Tomcat автоматически совместима с Boot 3.2.0 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<!-- Та же совместимая версия Tomcat -->
</dependency>
</dependencies>
</project>
Это гарантирует:
- Все зависимости совместимы между собой
- Автоматический выбор версий
- Наименее проблемный подход
Способ 4: Переопределение версии в properties
<project>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<!-- Переопределяем версию Tomcat, которую использует Spring Boot -->
<tomcat.version>10.1.5</tomcat.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<!-- Все стартеры автоматически используют переопределенную версию -->
</project>
Как это работает:
- Spring Boot Parent POM использует переменные для версий
- Переопределение в properties вашего pom.xml перезаписывает их
- Все транзитивные зависимости получают новую версию
Способ 5: Использование Gradle (если используешь Gradle)
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
}
// Переопределяем версию Tomcat
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.apache.tomcat') {
details.useVersion '10.1.5'
}
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// Теперь обе используют Tomcat 10.1.5
}
Способ 6: BOM Import (рекомендуется для non-parent POM)
Если ты не можешь использовать parent (например, у тебя уже есть другой parent):
<dependencyManagement>
<dependencies>
<!-- Import Spring Boot BOM для управления версиями -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Теперь все Spring Boot зависимости используют совместимые версии -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Версия берется из BOM -->
</dependency>
</dependencies>
Диагностика конфликтов
Найти какую версию Tomcat использует project:
# Maven
mvn dependency:tree -Dincludes=org.apache.tomcat:*
# Вывод:
# [INFO] org.springframework.boot:spring-boot-starter-web:jar:3.2.0
# [INFO] +- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.5
# [INFO] +- org.apache.tomcat.embed:tomcat-embed-el:jar:10.1.5
# Gradle
gradle dependencies | grep tomcat
Проверить совместимость явно:
mvn enforcer:enforce -Drules=
# Или использовать плагины проверки конфликтов
Практический пример: Разрешение конфликта
Сценарий: Используешь spring-boot-starter-web (Tomcat 10.0.x) и свой legacy код требует Tomcat 9.0.x.
Решение:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<!-- Понизить версию Tomcat для совместимости -->
<tomcat.version>9.0.70</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Будет использован Tomcat 9.0.70 -->
</dependency>
<!-- Legacy компонент требует старый Tomcat -->
<dependency>
<groupId>com.legacy</groupId>
<artifactId>legacy-servlet-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
Когда возникают проблемы?
-
Breaking changes между версиями Tomcat:
- Servlet API версии меняются (3.0, 4.0, 5.0 и т.д.)
- Классы могут быть перемещены в разные JAR'ы
- Конфигурация может измениться
-
Несовместимость с Java версией:
- Tomcat 10.x требует Java 11+
- Tomcat 9.x может работать на Java 8
-
Spring Boot версия несовместима:
- Spring Boot 2.x использует Tomcat 9.x
- Spring Boot 3.x использует Tomcat 10.x
Лучшие практики
Избегай:
- Смешивание разных версий Spring Boot в одном проекте
- Многоуровневого наследования parent POM
- Игнорирования warnings о конфликтах зависимостей
Делай:
- Используй Spring Boot BOM/Parent для управления версиями
- Явно указывай только необходимые версии
- Регулярно обновляй зависимости
- Проверяй совместимость при добавлении новых зависимостей
Инструменты помощи:
# Найти все конфликты
mvn dependency:analyze-duplicate
# Проверить security issues
mvn org.owasp:dependency-check-maven:check
# Update dependencies safely
mvn versions:display-dependency-updates
Основной вывод: Используй Spring Boot Parent POM или BOM, переопределяй версии только когда необходимо, и всегда проверяй совместимость перед commit'ом!