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. Це не так страшно, як може спочатку здаватися.
Favbet Tech – це ІТ-компанія зі 100% українською ДНК, що створює досконалі сервіси для iGaming і Betting з використанням передових технологій та надає доступ до них. Favbet Tech розробляє інноваційне програмне забезпечення через складну багатокомпонентну платформу, яка здатна витримувати величезні навантаження та створювати унікальний досвід для гравців.
Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: