Привет! Большинство разработчиков не спешат обновляться до новых версий Java. Многие опасаются, что все сломается, что появятся скрытые баги, что это займет очень много времени. Сегодня мы поделимся опытом перевода IGA-системы Solar InRights с Java 8 на актуальную Java 16, расскажем, для чего мы это сделали и почему именно сейчас. Подробно опишем, какие проблемы могут возникнуть при обновлении и как их устранить, а главное – поделимся тем, что мы в итоге получили.
Ранее в разработке мы использовали Java 8. Эта версия платформы и сейчас является достаточно стабильной и функциональной, в целом это понятная и удобная версия.
Однако в экосистеме произошло несколько важных событий, которые подтолкнули нас к переходу:
Была выпущена Java 9 со значительными архитектурными изменениями. Начиная с этой версии платформа Java является модульной, появляются разные выпуски JVM с различным набором фич. Эти изменения ломают обратную совместимость, и большинство программ, написанных на Java 8, не запускаются на Java 9+.
С выпуском Java 11 компания Oracle представила новую модель лицензирования и подписки. Теперь нужно особенно внимательно следить за тем, какую версию и где мы используем, чтобы не нарушить лицензию.
Релиз Java 16 включает множество новых возможностей в языке Java. Да, кто-то обязательно возразит, что аргумент слабый. Можно и дальше пользоваться Java 8, где все понятно, просто и удобно. Но с новыми синтаксическими конструкциями Java 16 писать код удобнее, а отслеживать ошибки проще. Наряду с уже перечисленными причинами это довольно приятный бонус.
У нас появилось свободное время на развитие системы, поэтому мы решили перейти на актуальный выпуск Java, и чем раньше это произойдет, тем легче будет процесс перехода. Версии с 11 по 16 называются промежуточными, потому что у них короткий период поддержки – 6 месяцев. На сентябрь 2021 года запланирован LTS (Long Term Support) релиз Java 17.
Так мы решили перейти на Java 16, чтобы затем быстро перейти на LTS Java 17.
С чем имеем дело
Система Solar inRights в собранном виде – это веб-приложение в виде war-файла, которое запускается на сервере Tomcat. Для сборки используем Maven. Код организован по функциональным модулям. Один модуль – это один maven-проект.
Система состоит из нескольких слоев:
Мы активно используем Spring для инверсии зависимостей, Hibernate для абстракции над логикой баз данных и JAXB для сериализации данных. Для написания тестов используем язык Groovy, а запускаем тесты с помощью TestNG.
В результате получается модульный монолит, в classpath которого собраны ресурсы от всех зависимостей.
Обновление на Java 16
На текущий момент новейшая стабильная версия JDK – это 16. Это preview release, значит некоторые языковые конструкции могут быть изменены или отсутствовать в следующей версии Java.
Мы решили, что будем собирать систему с помощью Liberica JDK 16 с русскоязычной поддержкой, но нам подойдет и OpenJDK, и любая открытая сборка JDK без специальных особенностей.
Просто взять и собрать систему на JDK 16 не получилось. Видим море ошибок, говорящих о том, что наш код обращается к закрытым частям платформы. Теперь нельзя обращаться к com.sun.*, java.security.* и прочим внутренностям JDK.
Изменяем работу с XML на использование открытых API.
Было:
import
com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;
import com.sun.org.apache.xml.internal.utils.XMLChar;
import com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl;
Стало:
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.xerces.util.XMLChar;
import org.w3c.dom.Node;
При сборке Maven отказывается читать наши Mojo, потому что не знает, как читать байткод классов, скомпилированных в Java 16. Обновляем версии зависимостей maven-plugin-tools (на текущий момент это 3.6.1):
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-tools-generators</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-tools-api</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-tools-annotations</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-tools-java</artifactId>
<version>3.6.1</version>
</dependency>
У Maven также будут проблемы с чтением конфигурации из javadoc. Исправляем формат javadoc в Mojo классах.
Было:
/** @parameter default-value="${project}" */
private MavenProject project;
Стало:
/**
* @parameter default-value="${project}"
*/
private MavenProject project;
Обновляем версию плагина Maven для запуска TestNG:
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-testng</artifactId>
<version>2.22.23.0.0-M5</version>
</dependency>
Теперь нужно обновить библиотеки проксирования и манипуляций с байткодом – все, что изменяет внутреннюю структуру классов. У нас получилось так:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
<version>2.6.0</version>
</dependency>
Поскольку Java 16 является preview версией, необходимо добавить параметр сборки --enable- preview. Для Maven можно создать файл .mvn/jvm.config со следующим содержанием:
--enable-preview
Это удобно, потому что опция записана в одном месте, и ее можно удалить, когда мы перейдем на LTS выпуск Java 17.
Запуск тестов показал, что внутри нам требуется доступ к инкапсулированным модулям платформы:
java.lang.IllegalAccessError: class ... (in unnamed module @0x568bf312)
cannot access class ... (in module java.base) because module
java.base does not export sun.security.util to unnamed module @0x568bf312
В несколько итераций получаем подходящий список параметров запуска:
--enable-preview
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.util.regex=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens java.base/java.util.stream=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.xml/javax.xml.datatype=ALL-UNNAMED
--add-opens java.xml/javax.xml.namespace=ALL-UNNAMED
--add-opens java.xml/com.sun.org.apache.xerces.internal.jaxp.datatype=ALL- UNNAMED
--add-exports java.management/sun.management=ALL-UNNAMED
--add-exports java.xml/com.sun.org.apache.xerces.internal.jaxp.datatype=ALL- UNNAMED
Последний шаг – обновить сервер приложений Tomcat. Сейчас новейшей версия – 10, и в ней разработчики требуют, чтобы мы переписали все использования javax.* на jakarta.* – однако нам подойдет Tomcat 9, его последние сборки тоже поддерживают Java 16.
Наконец, система полностью собрана на Java 16 и может быть запущена на любом JDK. Мы используем Liberica JDK и OpenJDK.
При тестировании мы увидели небольшие различия в поведении системы по сравнению с Java 8:
Форматирование XMLGregorianCalendar теперь использует формат, настроенный в системе по умолчанию, вместо единого заданного формата. Если мы хотим сохранить прежний формат, мы должны явно его указать.
Теперь не допускается хранение в resources файлов, имеющих кириллические символы в названии.
JVM теперь не завершит работу, пока все потоки программы не завершат выполнение. Это хороший повод разобраться с ThreadPool, потому что Tomcat не завершит работу до тех пор, пока они не будут закрыты.
Что дальше
В целом обновление прошло быстро, гладко, без сюрпризов. Что мы получили результате:
Больше не опасаемся лицензионных ограничений, связанных с Java 8.
Чуть более безопасную и производительную платформу с закрытыми уязвимостями, исправленными проблемами быстродействия, новыми фичами.
Новые синтаксические конструкции в языке Java, которые уже давно есть в Kotlin и Groovy (например, улучшенный instanceof, record-классы, текстовые блоки).
Возможность обновить сторонние зависимости, фреймворки, инструменты.
Java 16 является промежуточным обновлением платформы с коротким циклом поддержки. Мы ожидаем стабильного релиза Java 17 в сентябре, чтобы сразу перевести Solar InRights на LTS выпуск платформы. А это в свою очередь, вместе с обновлением фреймворков и других библиотек, повысит качество продукта в целом.
Автор: Олег Гетманский, архитектор информационных систем "Ростелеком-Солар"