Java 17 — новая LTS-версия — вышла на рынок почти год назад, но до сих пор не приобрела значительную популярность. О том, почему на нее следует перейти, я подробно рассказывал на конференции NIX MultiConf и в своей предыдущей статье на Highload.
Сегодня я опишу основные шаги и особенности миграции проекта с Java 11 в Java 17. Я буду пытаться рассмотреть возможные ошибки и варианты их решения.
Сразу подчеркиваю: я рассмотрю миграцию с программой Spring Boot. Это один из самых популярных фреймворков, которые используют большинство Java-разработчиков.
Правда, официальная поддержка Java 17 начинается с Spring 6 и Spring Boot 3, но эти версии еще в разработке. Последняя же версия Spring Boot — 2.6.7 . Ее и рассмотрим. Хотя она официально не поддерживается Java 17, разработчики Spring приложили много усилий, чтобы версии Spring Boot от 2.6.5 уже в определенной степени работали с новой LTS.
Как вообще мигрировать на любую Java-программу?
Для начала вам нужно в pom.xml
найти properties
и изменить версию с текущей на 17-ю. Это может выглядеть так:
<properties> <java.version>11</java.version> </properties>
на
<properties> <java.version>17</java.version> </properties>
Но, скорее всего, при использовании любых зависимостей у вас произойдет ошибка. Например, может появиться сообщение вроде:
error: release version 17 not supported
Поначалу может быть непонятно, почему так произошло. Поэтому попытаемся пошагово разобраться с проблемой и решить ее.
Lombok
Самая первая ошибка — это Lombok. Так называется Java-библиотека, автоматизирующая генерацию шаблонного кода. Она может генерировать гетеры, сеттеры, конструкторы, логирование и т.д., освобождая классы от загромождения шаблонным кодом. При миграции с 11-й на 17-ю версии Java вы получаете следующую ошибку:
java.lang.IllegalAccesError: class lombok.javac.apt.LombokProcessor cannot access classcom.sun.tools.javac.proseccing.JavacProcessingEnvironment
Все это касается версии Lombok 1.18.22. На момент миграции моего приложения у меня была версия 1.18.12. Чтобы понять причину этой ошибки, нужно взглянуть на JEP 396, который добавил суровую инкапсуляцию внутренних компонентов JDK по умолчанию.
Этот JEP был реализован еще в 16 версии Java, и именно из-за него у Lombok больше нет возможности использовать внутренние компоненты JDK.
Сначала разработчики Lombok решили использовать следующую команду для отмены предыдущего JEP:
--illegal-access=permit
Но это было действительно только для Java 16. В 17-й же версии эту команду убрали. Поэтому в Lombok придумали другое решение. Вам следует добавить в maven-compiler-plugin
аргументы. Таким образом вы даете доступ к нужным компонентам Lombok:
<compilerArgs> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg> </compilerArgs>
Но вручную вносить аргументы не очень удобно. Поэтому в Lombok выпустили версию 1.18.22, в которой это делается автоматически — вам достаточно добавить эту зависимость. Для этого разработчики с помощью рефлексии изменили видимость модулей — и уже после этого у вас есть доступ.
Да, это не очень секьюрно, но все же работает.
MapStruct
MapStruct — процессор аннотаций Java для автоматического генерирования преобразователей (mapper) между Java-компонентами.
MapStruct использует сгенерированные гетеры, сеттеры и конструкторы для создания преобразователей. После обновления Lombok до версии 1.18.22 преобразователи больше не создаются именно из-за той ошибки — из-за того, что был реализован JEP 396, прибавивший строгую инкапсуляцию внутренних модулей.
Чтобы обойти этот момент, необходимо добавить обработчик аннотаций lombok-mapstruct-binding
к maven-compiler-plugin
:
<path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path>
После этого все должно работать без проблем.
ASM
ASM — это среда для манипулирования байтовым кодом Java. ASM использует CGLIB, который, в свою очередь, используется Spring для AOP.
В Spring Framework AOP-прокси — это динамический прокси JDK или прокси CGLIB. Spring использует CGLIB и ASM, генерирует прокси-классы, несовместимые со средой выполнения Java 17. Spring Boot ниже 2.4 и зависит от Spring Framework 5.2, который использует версию CGLIB и ASM, несовместимую с Java 17.
Но обновление библиотек CGLIB или ASM невозможно, поскольку Spring переупаковывает ASM для внутреннего использования. То есть единственный выход из этой ситуации — обновить Spring Boot. Без этого ASM не заработает.
JUnit и отсутствующее свойство spring-boot.version
В новой версии Spring Boot spring-boot-dependencies
было удалено свойство spring-boot.version
. Разработчики использовали ее для многих задач, скажем, чтобы исключать какую-либо зависимость.
Вот пример с исключением junit-vintage-engine
:
<dependencyManagement> <dependencies> <dependecy> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependecy> </dependencies> </dependencyManagement>
К счастью, теперь мы можем удалить этот блок, поскольку Spring Boot 2.4 удалил Vintage Engine JUnit 5 со стартера spring-boot-starter-test
.
Но если ваш проект до сих пор использует JUnit 4 и вы видите ошибки компиляции вроде java: package org.junit does not exist
— это потому что старый движок был удален.
Старый движок отвечает за выполнение тестов JUnit 4 вместе с тестами JUnit 5. Если вы не можете из-за каких-либо обстоятельств перенести тесты на JUnit 5, добавьте в pom
следующую зависимость:
<dependecy> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependecy>
Это полностью решит описанную проблему.
Jackson
Следующая возможная проблема связана с Jackson. Jackson — это библиотека инструментов обработки данных, например, для сериализации и десериализации JSON в компонентах Java и наоборот. Она может обрабатывать много форматов данных, но чаще всего ее используют для работы с JSON.
После обновления в Spring Boot 2.6.7 возникает следующая ошибка:
java.time.OffsetDateTime not supported by default: add Module “com.fasterxml.jackson.datatype:jackson-datatype-jsr310”
Причина — модуль JSR-310 недоступен для Jackson. Из-за смены Jackson 2.12 теперь это приводит к сбою сериализации, а не к тому, чтобы Jackson сериализировался в неожиданном формате.
Есть два решения этой проблемы. Вы можете создать mapper
сами. Для версии 2.10 и более поздней через JsonMapper.buider это выглядит следующим образом:
ObjectMapper mapper = JsonMapper.buider() .addModule(new JavaTimeModule()) .build();
Для более старой версии это можно сделать через mapper.registerModule
:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule());
Но все это не очень хороший вариант, поскольку создание мапперов собственными руками требует лишнего времени и усилий. Поэтому предлагаю другой способ — попробовать автоконфигурацию Jackson.
Автоконфигурация Jackson
Spring Boot обеспечивает автоконфигурацию Jackson и автоматически объявляет полностью настроенный компонент ObjectMapper
. Jackson можно настроить без определения собственного Bean-компонента ObjectMapper
, используя свойства или класс Jackson2ObjectMapperBuilderCustomizer
.
Необходимо проверить, что модуль com.fasterxml.jackson.datatype:jackson-datatype-jsr310
находится в дороге к классам (т.е. в classpath
) — и он будет автоматически зарегистрирован в ObjectMapper
.
ObjectMapper
потокобезопасен, поэтому его можно создать один раз и использовать повторно.
Валидатор запросов Swagger от Atlassian
Swagger от Atlassian представляет собой библиотеку для валидации запросов и ответов Swagger/OpenAPI 3.0. Старая версия библиотеки не использует автоконфигурацию Spring Boot Jackson и не регистрирует JavaTimeModule
в своем ObjectMapper.
Но после обновления версии библиотеки тесты снова работают. Поэтому обязательно нужно использовать последнюю версию библиотек, обычно содержащую все необходимые исправления.
Вместо вывода
В общем, это наиболее заметные ошибки, с которыми я столкнулся при переходе на Java 17 и которые мне показались достойными внимания.
Конечно, на практике проблем может быть больше, но они в большинстве своем незначительны и требуют не столько особых подходов, сколько внимательности и дополнительного времени для решения. На мой взгляд, это все оправдывается преимуществами последней LTS-версии: она более функциональна и быстра.
Поэтому не бойтесь мигрировать свои проекты из Java 11 на Java 17. Это не так страшно, как может сначала казаться.
Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: