Привет, Хабр! Я Артем Киреев, ИТ‑инженер в СберТехе. Мы с командой занимаемся развитием продукта из состава Platform V Synapse — децентрализованной платформы для задач интеграции.
Мы стремимся поддерживать актуальность стека, на котором разрабатываем наши продукты, и регулярно отслеживаем все изменения. Обратившись к таблице поддерживаемых версий на официальном сайте Spring, мы обнаружили, что версии Spring Boot ниже 3.0 больше не поддерживаются. Поэтому мы решили, что нужно перевести проект на последнюю из существующих версий. На момент написания статьи это Spring Boot 3.2.
В ходе миграции я столкнулся с рядом проблем, решение которых не всегда было очевидным. Хочу помочь читателям быстрее и проще решить ошибки, с которыми столкнулся я сам.
Java
Наш проект использовал Java 11, а Spring Boot 3 требует Java 17, поэтому в первую очередь обновим именно её.
Для установки Java советую воспользоваться SDKMAN — инструментом для управления различными версиями Java.
Обновление до Spring Boot 3
Теперь мы можем обновить заветную зависимость org.springframework.boot
до последней версии. Всегда рекомендуется обновляться до последней версии Spring Boot 3, которая доступна для обслуживания. Если ваш проект использует сильно устаревшую версию, то лучше сначала перейти на 2.7.
Совершив этот отважный шаг, вы отправляетесь в захватывающее путешествие до вновь работающего проекта, а по пути вам придётся решить ещё немало загадок.
Gradle
Первое, что мы попробуем сделать, — это запустим сборку нашего проекта. Вдруг всё будет работать и так. Возвращаемся в реальный мир и знакомимся с первой ошибкой:
PermittedSubclasses requires ASM9
Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'machine-project-root'.
Caused by: org.gradle.internal.UncheckedException: java.util.concurrent.ExecutionException: org.gradle.api.GradleException: Failed to create Jar file /Users/name/.gradle/caches/jars-9/5bef74195685ec65a206425cc0cfd949/spring-core-6.0.8.jar.
Caused by: java.util.concurrent.ExecutionException: org.gradle.api.GradleException: Failed to create Jar file /Users/name/.gradle/caches/jars-9/5bef74195685ec65a206425cc0cfd949/spring-core-6.0.8.jar.
Caused by: org.gradle.api.GradleException: Failed to create Jar file /Users/name/.gradle/caches/jars-9/5bef74195685ec65a206425cc0cfd949/spring-core-6.0.8.jar.
Caused by: java.lang.UnsupportedOperationException: PermittedSubclasses requires ASM9
Она возникает из-за несовместимости текущей используемой версии Gradle и той, что используется для плагина Spring Boot 3. Согласно документации на плагин требуется версия Gradle не ниже 7.5.
Обновляем версию Gradle. В своем проекте я обновился до 7.6.
Jakarta EE
Если с версией Gradle всё в порядке, то переходим к следующей причине ошибки сборки проекта.
Из-за того, что Spring Boot 3 использует Java 17, Java EE в ней был заменён на Jakarta EE. Следовательно, необходимо проверить классы сущностей/ДТО, да и весь проект в целом, на использование аннотаций из пакета javax. В большом проекте этот этап может занять достаточно много времени. Всё будет зависеть от количества классов, которые используют эти аннотации.
Если вы управляете собственными зависимостями и не полагаетесь на стартовые зависимости Spring, вам также следует убедиться, что вы обновились соответствующим образом.
Обратите внимание на то, чтобы старые зависимости Java EE больше не использовались напрямую или транзитивно в вашей сборке. Например, зависимость javax.servlet:javax.servlet-api
должна измениться на jakarta.servlet:jakarta.servlet-api
.
HttpClient
Spring Boot 3 использует зависимости Spring 6, и если вы используете библиотеку spring-web, то обратите внимание на переход с org.apache.httpcomponents:httpclient
на org.apache.httpcomponents.client5:httpclient5
. При управлении собственными зависимостями обновитесь соответствующим образом.
Если вы используете HttpClient напрямую вместо RestTemplate, то обратите внимание, что теперь вы не сможете воспользоваться созданием сущности HttpClient
из SSLContext
.
HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
Теперь этот процесс выглядит так:
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
.setSslContext(sslContext)
.build();
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactory)
.build();
HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
@ConstructingBinding
При получении следующей ошибки
error: annotation type not applicable to this kind of declaration
@ConstructorBinding
^
проверьте использование аннотации @ConstructorBinding
в проекте. Она теперь не нужна на уровне классов в классах с аннотацией @ConfigurationProperties
и должна быть удалена. Если же у класса несколько конструкторов, то @ConstructorBinding
всё ещё может использоваться в конструкторе, чтобы указать, какой из них следует использовать для привязки свойств.
Spring boot-стартеры
При попытке запуститься мы можем столкнуться с ошибкой создания бинов. Вероятно, это связано с изменениями в регистрации автоконфигураций.
Ещё в Spring Boot 2.7 появился новый файл для регистрации автоконфигураций при сохранении обратной совместимости с регистрацией в spring.factories META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. В Spring Boot 3 регистрация автоконфигураций в spring.factories с использованием аннотации @EnableAutoConfiguration
была удалена в пользу файла imports. Этот файл создаётся в папке resources и содержит путь до класса конфигурации ru.sbt.ss.edms.StarterConfiguration
.
SecurityConfig
При переходе на Spring Boot 3 нужно провести рефакторинг класса SecurityConfig. Начиная с Spring 6.1 кастомизация HttpSecurity без использования лямбд помечена как @Deprecated
. Вот пример кастомизации с использованием лямбд:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(
(authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers("/config").hasRole("ADMIN")
.requestMatchers("/home").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
Также обратите внимание на то, что теперь при кастомизации authorizeHttpRequests
используется метод requestMatchers()
вместо antMatchers()
.
Springdoc API
Если вы используете на своем проекте SpringDoc, который упрощает создание и обслуживание документов API на основе спецификации OpenAPI 3, то вам нужно обновить зависимость, которая теперь выглядит следующим образом:
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui"
Обновлённая зависимость позволит переопределять типы переменных прямо из классов сущностей/ДТО. Для этого нужно добавить аннотацию @Schema
над полем и определить его тип, который будет отображаться в OpenAPI:
public class SortRequestDto {
//..//
@Schema(implementation = ColumnType.class)
private String column;
//..//
}
Базы данных и testcontainers
Если вы делаете интеграционные тесты в своём проекте, то после миграции на Spring Boot 3 вы можете столкнуться с ошибками Hibernate в случае, если используете устаревшую версию базы данных. Во время своей миграции я столкнулся с подобным в интеграционных тестах на пагинацию. Используемая на тот момент PostgreSQL 9 получала неправильный запрос и выдавала ошибку. Решать это нужно обновлением до новейших версий используемой вами базы данных. В своём проекте я обновился до PostgreSQL 15.5. Для этого нужно скачать пакеты выбранной версии с сайта PostgreSQL или воспользоваться соответствующим Docker-образом.
Резюме
Уровень сложности миграции может разниться в зависимости от модулей Spring, которые вы интегрируете в свое приложение. Я потратил немало времени на поиск информации для решения подобных задач. Так что, надеюсь, эта статья облегчит вам задачу и поможет пройти этот путь быстрее. А если в процессе миграции вы столкнулись с другими проблемами, смело делитесь ими в комментариях.
Вообще нужно сказать, что поддержка зависимостей на проекте в актуальном состоянии — однозначно хорошая практика. И, чтобы упростить задачу завтрашнему себе, стоит не забывать, что процесс обновления становится более плавным, если переходить от одной версии к другой без пропусков.