Перенос проекта с Java 11 на Java 17: пошаговое руководство для разработчиков

Никита Земницкий, Java-разработчик в компании NIX.

Java 17, новая версия LTS, вышла на рынок почти год назад, но пока не завоевала значительной популярности. По данным опроса NewRelic, эту версию Java сейчас используют 0,37% разработчиков. Многие программисты задумываются о возможных проблемах при переходе с 11-й версии LTS на свежую версию. В конце концов, многие помнят, насколько тяжелым был переход с 8 на 11. Лично я уже убедился: теперь все по-другому. Переход на новую Java не так сложен, как кажется. Для разработчика есть множество преимуществ, начиная от использования новых возможностей и применения языка и заканчивая повышением скорости работы в целом. Я думаю, что в дальнейшем все больше разработчиков захотят попробовать Java 17.

Я занимаюсь разработкой на Java уже более трех лет, работая над приложениями микросервисов. В этой статье я опишу основные шаги и особенности миграции проекта с 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 и изменить версию с текущей на 17-ю. Это может выглядеть следующим образом:

<properties>
<java.version>11</java.version>
</properties>

.

<properties>
<java.version>17</java.version>
</properties>

Однако, скорее всего, вы получите ошибку, если используете какие-либо зависимости. Например, вы можете получить сообщение вида:

error: release version 17 is 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 для автоматической генерации конвертеров (mappers) между компонентами Java. MapStruct использует сгенерированные геттеры, сеттеры и конструкторы для создания преобразователей. После обновления Lombok до версии 1.18.22 конвертеры перестали генерироваться именно из-за этой ошибки, поскольку был реализован JEP 396, добавляющий строгую инкапсуляцию внутренних модулей. Чтобы обойти это, необходимо добавить обработчик аннотации Lombok-map struct-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.version было удалено из spring-boot-dependencies. Разработчики использовали его для многих задач, например, для исключения зависимости. Вот пример с исключением 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 не поддерживается по умолчанию: добавить модуль «com.fasterxml.jackson.datatype:jackson-datatype-jsr310».

Причина в том, что модуль JSR-310 недоступен для Jackson. Из-за изменений в Jackson 2.12 теперь это приводит к сбою сериализации, а не к тому, что Jackson сериализует в неожиданном формате.

Есть два решения этой проблемы. Во-первых, вы можете создать свой собственный маппер. Для версии 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 можно настроить без определения собственного компонента ObjectMapper Bean, используя свойства или классы 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. Это не так страшно, как может показаться вначале.

Оцените статью
Procodings.ru
Добавить комментарий