21 жовтня – JS Conference 2017 у Києві. Як уникнути критичних помилок при створенні нового проекту?
×Закрыть

Проблемы при переходе на Java 9

В конце мая я выступал на JEEConf 2017, где рассказывал об эффективности и производительности Java библиотек и фреймворков. Любое упоминание о производительности должно ссылаться на результаты измерений, что я и сделал во время доклада. Но я решил пойти дальше и протестировать один и тот же код на двух версиях — Java 8 и Java 9.

Хоть Java 9 и выходит только в июле (уже в сентябре), но она уже перешла в стадию Release Candidate, то есть никаких новых фич больше не будет и все критические баги должны быть исправлены. Я начал пользоваться Java 8 за 5 месяцев до релиза и никаких особых проблем при этом не испытал. В то же время главная фича новой версии — модуляризация (проект Jigsaw) привела к многочисленным изменениям внутри самой JDK. Хорошо было бы протестировать библиотеки на новой версии. В сети есть много статей и видео-докладов на тему проектирования модулей. Но сначала нужно проверить, а будет ли работать существующее приложение, и если будет, то как отразится миграция на скорости работы нашей системы. Не секрет, что многие компании остаются на старых версиях (Java 5 — 7) частично из-за боязни новых ошибок и проблем в производительности. Если же приложение работать не будет, насколько просто это починить?

Поэтому я призываю всех установить JDK 9 и протестировать свое приложение. Разумеется, вы можете и дальше использовать текущую версию Java, но прогресс не стоит на месте. Через пару лет выйдет Java 10, а с ней возможно и свойства, и value types, и все равно придется использовать модульность. Тем более что современные IDE (IntelliJ IDEA) уже поддерживают все фичи Java 9.

Системы сборки. Maven

Сборка проекта — это процесс, который проходит постоянно в течение рабочего дня как на компьютере разработчика, так и на CI сервере. Поэтому здесь требуется максимальное быстродействие. Для тестирования сборки я выбрал проект, который я разрабатываю в рамках книги «Разработка Java приложений», тем более что он поддерживает и Maven, и Gradle. Проект постоянно обновляется, поэтому версии зависимостей в нем 2017-го года либо конца 2016-го года. Вы сами можете скачать проект и протестировать на своей машине.

Поддерживают ли Maven/Gradle Java 9? Это интересный вопрос, тем более что их модель зависимостей чем-то похожа и даже является конкурентом JDK 9.

Итак, я установил JDK Early Access preview build 171 на Windows систему. Результат запуска команды «java -version»:

java version "9-ea"
Java (TM) SE Runtime Environment (build 9-ea+171)
Java HotSpot (TM) 64-Bit Server VM (build 9-ea+171, mixed mode)

Начнем с Maven. Для тестирования я выбрал последнюю версию — 3.5.0. Сам Maven запускается без проблем, выдает корректную версию JDK:

Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-03T22:39:06+03:00)
Maven home: C:\Maven\bin\..
Java version: 9-ea, vendor: Oracle Corporation
Java home: C:\Program Files\Java\jdk-9

Теперь запустим сборку проекта: mvn clean install. Сборка падает на этапе запуска тестов с ошибкой:

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:553)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:486)
	... 33 more

Почему-то стандартный класс JAXBException больше не находится. Дело в том, что все, связанное с JAXB, перенесено 9-й Java в модуль java.xml.bind, который автоматически не добавляется в modulepath при запуске JVM, потому что не входит в дефолтный модуль java.se. Как выйти из этого положения? Либо явно указать этот модуль для запуска JVM, либо агрегатный модуль java.se.ee, куда входит все, что связано с Java EE API. Разумеется, добавить агрегатный модуль удобнее, чем каждый модуль по отдельности. Поэтому добавляем переменную окружения MAVEN_OPTS (если у вас ее еще нет), и присваиваем ей значение «—add-modules java.se.ee».

Однако ошибка не исчезает. Все дело в том, что тесты запускает Maven Surfire плагин, который стартует новый JVM процесс. Для того, чтобы указать новые модули, нужно добавить Surfire плагин в главный pom.xml и передать параметры там:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.20</version>
    <configuration>
        <argLine>--add-modules java.se.ee</argLine>
    </configuration>
</plugin>

Перезапускаем сборку и получаем новое исключение:

java.lang.NoClassDefFoundError: javax/transaction/SystemException
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:374)
	at org.jboss.logging.Logger$1.run(Logger.java:2554)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at org.jboss.logging.Logger.getMessageLogger(Logger.java:2529)

Эта проблема не имеет очевидного решения, потому что JTA библиотека (где находится класс SystemException) присутствует в class-path. Поэтому придется все же в Surfire плагине использовать модуль java.xml.bind:

<configuration>
        <argLine>--add-modules java.xml.bind</argLine>
</configuration>

Перезапускаем сборку Maven и получаем новое исключение:

Caused by: java.lang.reflect.InvocationTargetException
	at org.itsimulator.germes.app.persistence.repository.hibernate.HibernateCityRepositoryTest.setup(HibernateCityRepositoryTest.java:18)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to un-named module @5807efad

В чем заключается ошибка? Hibernate пытается через reflection сделать публичным метод, который является protected или private. До Java 9 это было небезопасно, но возможно. Сейчас это запрещено, если только модуль, в котором находится метод, не объявлен открытым (open). Однако есть лазейка, помогает обойти это ограничение. Нужно добавить к аргументам JVM строку —add-opens java.base/java.lang=ALL-UNNAMED

Здесь мы указываем название модуля, затем пакет и тот модуль, для которого мы открываем доступ. Так как наш проект не использует модули, то мы указываем константу ALL-UNNAMED, которой открыт доступ для всех анонимных модулей. Анонимные модули — это обычные jar файлы, в которых нет файла конфигурации module-info.class. Они были добавлены, чтобы разработчикам библиотек было легче перейти на Java 9.

Добавляем эту строку к аргументам командной строки для плагина Surfire и перезапускаем сборку Maven. Получаем новое исключение, но уже при запуске других тестов:

Caused by: java.io.IOException: Can not attach to current VM
        at jdk.attach/sun.tools.attach.HotSpotVirtualMachine.<init>(HotSpotVirtualMachine.java:75)
        at jdk.attach/sun.tools.attach.VirtualMachineImpl.<init>(VirtualMachineImpl.java:48)
        at jdk.attach/sun.tools.attach.AttachProviderImpl.attachVirtualMachine(AttachProviderImpl.java:69)
        at jdk.attach/com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:207)
        at mockit.internal.startup.AgentLoader.attachToRunningVM(AgentLoader.java:146)
        ... 20 more

Как вы видите из stacktrace, проблема происходит на стадии инициализации JMockit. Что можно сделать? Можно обновить версию Jmockit с 1.30 на 1.32. Но это не помогает. Уже готовится 2-я версия JMockit, но она все еще в стадии beta. Анализ исходников JDK позволяет обнаружить системную переменную JVM, которая позволяет присоединиться к текущей JVM из нее самое. Нужно лишь установить этот флаг в true: -Djdk.attach.allowAttachSelf=true. Подробнее можно прочесть здесь

Добавляем флаг к параметрам в Surefire плагине и перезапускаем сборку Maven. Получаем новую ошибку:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.6:war (de-fault-war) on project germes-presentation: Execution default-war of goal org.apache.maven.plugins:maven-war-plugin:2.6:war f12:37
: Unable to load the mojo 'war' in the plugin 'org.apache.maven.plugins:maven-war-plugin:2.6' due to an API incompatibility: org.codehaus.plexus.component.repository.exception.ComponentLookupException: null

Подробнее об этой проблеме можно прочесть здесь.

А лечится она обновлением версии war-плагина с 2.6 на 3.1.0:

<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.1.0</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>

Меняем версию и перезапускаем сборку. Получаем новую ошибку:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.1:compile (default-compile) on project germes-admin: Fatal error compiling: java.lang.ExceptionInInitializerError: Unable to make field private com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs accessible: module jdk.compiler does not "opens com.sun.tools.javac.processing" to unnamed module @79296ce0

С похожей проблемой мы уже сталкивались. Нужно лишь «открыть» доступ к пакетам, добавив параметры в MAVEN_OPTS:

--add-opens jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED

Перезапускаем сборку Maven и получаем новую ошибку:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.1:compile (default-compile) on project germes-admin: Fatal error compiling: java.lang.NoClassDefFoundError: com/sun/tools/javac/file/BaseFileObject: com.sun.tools.javac.file.BaseFileObject

Stacktrace говорит об ошибке более подробно:

Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.file.BaseFileObject
        at lombok.launch.ShadowClassLoader.loadClass(ShadowClassLoader.java:422)

Проблема в том, что такого класса больше нет в JDK. Можно вручную добавить необходимые классы, как сделали здесь.

А можно обновить версию Lombok (c 1.16.14 до 1.16.16):

<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.16</version>
		</dependency>

Перезапускаем сборку Maven и получаем новое исключение при запуске тестов:

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

После небольшого исследования выяснилось, что проблема исходит от библиотеки тестирования JGlue, однако его последняя версия была выпущена в июле 2016 года и неизвестно, насколько быстро выйдет новая версия.

На этом мое тестирование Maven завершилось. Часть проблем удалось решить, но в целом, их довольно много как для одного проекта. Проект собирается успешно, но часть тестов при этом не проходит.

Системы сборки. Gradle

Теперь проверим, насколько совместим Gradle с JDK 9. Возьмем последнюю версию первого — 3.5.0, обновим Lombok зависимость и запустим сборку: gradle clean install.

Получим довольно малоинформативную ошибку: java.lang.ExceptionInInitializerError (no error message). Поэтому добавим флаг —stacktrace, который даст больше информации:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected java.lang.Package[] java.lang.ClassLoader.getPackages() accessible: module java.base does not "opens java.lang" to unnamed module @57c03d88
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
        at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:197)
        at java.base/java.lang.reflect.Method.setAccessible(Method.java:191)
        at org.gradle.internal.reflect.JavaMethod.<init>(JavaMethod.java:42)
        at org.gradle.internal.reflect.JavaMethod.<init>(JavaMethod.java:32)

Для того, чтобы избавиться от этой ошибки, добавляем знакомый аргумент add-opens в системную переменную GRADLE_OPTS:

--add-opens java.base/java.lang=ALL-UNNAMED

Подробнее можно прочитать здесь и здесь.

Перезапускаем сборку Gradle, получаем новую ошибку:

* Exception is:
org.gradle.api.GradleException: Unable to start the daemon process.
This problem might be caused by incorrect configuration of the daemon.
        at org.gradle.launcher.daemon.bootstrap.DaemonGreeter.parseDaemonOutput(DaemonGreeter.java:34)
        at org.gradle.launcher.daemon.client.DefaultDaemonStarter.startProcess(DefaultDaemonStarter.java:152)
        at org.gradle.launcher.daemon.client.DefaultDaemonStarter.startDaemon(DefaultDaemonStarter.java:135)
        at org.gradle.launcher.daemon.client.DefaultDaemonConnector.startDaemon(DefaultDaemonConnector.java:208)
        at org.gradle.launcher.daemon.client.DefaultDaemonConnector.connect(DefaultDaemonConnector.java:130)
        at org.gradle.launcher.daemon.client.DaemonClient.execute(DaemonClient.java:127)

В результат длительного исследования оказывается, что ситуация не так проста, как для Maven. Дело в том, что во время сборки в Gradle запускается несколько JVM процессов, одни процессы могут порождать другие. Можно сделать fork процессов и главное, чтобы все они использовали одни и те же аргументы JVM. С помощью GRADLE_OPTS добиться этого невозможно. Поэтому была придумана новая системная переменная JDK_JAVA_OPTIONS, которая используется всеми JVM процессами в Gradle (подробней о ней здесь). Более того, начиная со сборки b157 появилась системная переменная JDK_JAVAC_OPTIONS для передачи параметров в Java компилятор (javac). Установим JDK_JAVA_OPTIONS в следующее значение:

--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED

И перезапустим сборку. У нас выпало исключение:

> java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x2d8aa614) cannot access class com.sun.tools.javac.util.Context (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.util to unnamed module @0x2d8aa614

Такой ошибки у нас еще не было. Суть ее в том, что мы хотим получить доступ к классам из JDK, которые находятся в пакетах, не указанных модулем как экспортируемых (exported) в module-info.java. Это можно обойти, если добавить в JAVA_JDK_OPTIONS «—add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED»

Теперь сборка прошла успешно. Запустим тесты: gradle test. Тесты посыпались с теми же ошибками, что и в Maven. Это можно исправить, просто указав аргументы JVM в блоке test:

   test {
     jvmArgs '--add-modules', 'java.xml.bind', '-Djdk.attach.allowAttachSelf=true'
  }

Мы все еще используем source/target compatibility 1.8 в нашем проекте. Если мы поменяем их на 1.9, то при компиляции получим ошибку:

error: package javax.annotation is not visible
import javax.annotation.PreDestroy;
            ^
  (package javax.annotation is declared in module java.xml.ws.annotation, which is not in the module graph)
1 error

Все потому что мы перешли на 1.9, а значит должны оформлять наши проекты в модули, писать module-info.java и в них указать, чтобы нам нужен (requires) тот или иной модуль из JDK. Ситуация становится интересней для Maven/Gradle зависимостей. Здесь можно прочесть о том, как конфигурировать модули, если они содержат одинаковые пакеты.

Однако пока Gradle еще не полностью готов к Java 9, вы можете запускать его под Java 8, а компилировать проект под Java 9. Для этого нужно в скрипте сборки указать конфигурацию под 9-ю версию:

compileJava {
    sourceCompatibility = '9'
    targetCompatibility = '9'
    options.with {
        fork = true
        forkOptions.javaHome = new File ('c:\Program Files\Java\jdk-9')
        compilerArgs.addAll(['--add-exports', 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'])
    }
}

Тестирование REST-контейнеров

Так как я в своем докладе рассказывал про REST-контейнеры и веб-сервера, то интересно будет их проверить на совместимость с Java 9, тем более что это достаточно сложные технологии.

Начнем с Ratpack. Это довольно новый сервер, полностью написанный на Java 8 и основанный на Netty. Если мы запустим его, то получим исключение:

Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/MimetypesFileTypeMap
	at rat-pack.file.internal.ActivationBackedMimeTypes.<clinit>(ActivationBackedMimeTypes.java:34)
	at ratpack.server.internal.ServerRegistry.buildBaseRegistry(ServerRegistry.java:99)
	at ratpack.server.internal.ServerRegistry.serverRegistry(ServerRegistry.java:63)

Нам нужно лишь добавить модуль java.activation при старте сервера, чтобы это починить:

--add-modules java.activation

Теперь перейдем к Apache CXF. Для него нужно указать другой модуль:

--add-modules java.xml.bind

Для запуска приложения с Jersey (JAX-RS) я использую Jetty сервер. Jersey тоже требует предыдущий модуль.

Я думаю, что вы знаете либо слышали, что летом этого года выходят Spring Framework 5 и Spring Boot 2.0, специально приуроченные к выходу JDK 9 и даже использующие некоторые ее возможности. Пока они не вышли, интересно проверить текущие версии (Spring Boot 1.5.3 и Spring Framework 4.3.8) на «запускаемость».

Spring Boot поддерживает три встроенных сервера: Tomcat, Jetty и Undertow. К сожалению, у меня не получилось запустить REST-приложение ни на одном из них. Оно просто зависало на стадии загрузки.

JMeter — одна из наиболее популярных утилит для тестирования веб-приложений. Она может работать как в консольном варианте, так и в режиме с UI. Готово ли оно к Java 9? Если мы возьмем последнюю версию (3.2) и запустим jmeter.bat, то увидим в консоли ошибку:

Not able to find Java executable or version. Please check your Java installation.
errorlevel=2

Это достаточно странное сообщение, которое судя по всему печатает не JVM. Если мы заглянем в исполняемый файл, то увидим целые блоки кода, где сначала выполняется команда «java-version», потом ее результат парсится и из него выделяется старшая и младшая версии. Причем JMeter предполагает, что результат будет в формате 1.8.0.

Так как в Java 9 результат уже в формате 9.0.0 (openjdk.java.net/jeps/223), то это приводит к катастрофическим последствиям и аварийному завершению приложения. Причем JMeter даже не пишет, что версия неправильная, а просто говорит, что не может найти Java.

Выход заключается в том, чтобы удалить все эти проверки и тогда утилита запустится корректно. Однако при загрузке конфигурации мы получим исключение:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @63fbfaeb
	at java.lang.reflect.AccessibleObject.checkCanSetAccessible(Unknown Source) ~[?:?]
	at java.lang.reflect.AccessibleObject.checkCanSetAccessible(Unknown Source) ~[?:?]

Для того, чтобы избежать подобных ошибок, нужно лишь добавить в JVM аргументы в скрипте строку:

--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED

Заключение и спасительный флаг

Как вы видите, в последний год вендорами была проделана большая работа по обеспечению совместимости с Java 9. Это тем более было трудно, что даже в этом году были некоторые изменения в API JDK. Тем не менее многие библиотеки уже успешно работают над Java 9, некоторые без каких-либо ухищрений, некоторые с дополнительными аргументами для JVM.

Тем не менее, выяснять и разбираться в настройках не так приятно, поэтому в конце марта 2017 года руководство Oracle решило упростить жизнь разработчикам и добавить новый спасительный флаг, который упростит миграцию на Java 9, но будет удален в Java 10.

Этот флаг —permit-illegal-access разрешает доступ всем анонимным модулям через reflection к коду именованных модулей и позволяет не писать многочисленные —add-opens. Единственный минус этого флага — он будет выводить предупреждения в консоль обо всех случаях несанкционированного доступа.

Однако оказалось, что, хотя этот флаг и избавляет от необходимости указывать многочисленные —add-opens, он не избавляет от необходимости указывать самого себя. Поэтому начиная со сборки b172 его предполагается заменить на флаг —illegal-access. У этого флага будет четыре значения:

  • permit. Это значение по умолчанию, которое открывает доступ через reflection из всех анонимных модулей во все пакеты именованных модулей. При этом значении только один раз печатается предупреждение о попытке доступа.
  • warn. Каждая попытка получить доступ через reflection приводит к выводу предупреждения.
  • debug. Здесь к выводу предупреждения выводится еще и stacktrace операции.
  • deny. Планируется сделать дефолтным в Java 10. Запрещает доступ через reflection для всех пакетов в именованных модулях, кроме указанных явно в —add-opens.

Подробнее можно прочесть здесь. Интересно, что на момент сборки b173 этот флаг все еще не добавлен.

Я надеюсь, что вам было полезно это руководство и миграция ваших приложений будет более простой и быстрой.

LinkedIn

33 комментария

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

А я уверен что адопшен девятки, пусть и с болью, но будет большим. Потому что, как это не парадоксально звучит, они ломают обратную совместимость чтобы потом была лучшая обратная совместимость. Просто многие даже не догадываются какой жёсткий dependency hell происходит в их энтерпрайзном легаси проекте, пока не попробуют что-то обновить. Тысячи дублированных классов (причём разных версий) которые одновременно тусуются в класслоадере и другие приключения.
Использование OSGI обычно всё ещё только усложняет.
По сути все забили с этим разбираться и частично отсюда успех Spring Boot в котором версии уже подогнаны по совместимости. И на практике я не знаю лучшего способа чем тупо резать на микросервисы где класслоадер гарантированно у каждого свой.
Кстати Докер он же о том же по сути, про изоляцию процесса с его зависимостями.

Большие корпоративные проекты будут долго мигрировать либо работать с костылями-флагами.
Либы мигрируют быстро. А новые проекты, обычно микросервисы, и не заметят никаких проблем.

А я уверен что адопшен девятки, пусть и с болью, но будет большим.

Адопшн будет :) Но вот по поводу большого, что и куда важнее, быстрого, я не уверен. Основная причина:

Просто многие даже не догадываются какой жёсткий dependency hell происходит в их энтерпрайзном легаси проекте, пока не попробуют что-то обновить.

Нужно обястнить бизнесу «нафика на это тратить деньги».
В мире всяких спрингбутов все быдет намного быстрее, ибо там большую часть гемора возьмет на себе Пивотал.

Так это только поблемы со сборкой. А что будет с запуском приложения? Вот некоторый анализ в основном от разработчиков redhat’а. Так что переход на 9 джаву будет долгим и болезненым.

Вот некоторый анализ в основном от разработчиков redhat’а.

Это не анализ, а «ой, мы тут 2 года хной занимались и теперь не знаем куда всунуть ДжБосс Модули» :)

Так эти проблемы будут не только у джбосс разработчиков и не только в каких-либо серверах, но и в прикладных приложениях.

А вообще эту ситуацию можно охарактеризовать так — мы тут гарантируем обратную совместимость, но в java 9, ОЙ, сделаем исключение. Я в принципе не против поломки совместимости на майорных версиях не чаще раз в 2 года, но как то напрягает избирательное ОЙ.

мы тут гарантируем обратную совместимость, но в java 9, ОЙ, сделаем исключение.

Как человек переживший миграцию с 6 на 7 и с 7 на 8 и видевший миграции с 4 на 5 и с 5 на 6, могу сказать что никакой обратной совместимости нет. От только несовместимость вызвана не платформой, а низкокачественным кодом (который эксплоатировал недокументированное поведение). Та и гарантируют бинарную совместивость, даже не компиляции (при переходе то ли с 7 на 8, то ли с 6 на 7 был код который не компилировался из-за странного использования дженериков).

Так эти проблемы будут не только у джбосс разработчиков и не только в каких-либо серверах, но и в прикладных приложениях.

Проблемы будут у тех кто разрабатывал тулинг, но они их по большей части порешали. У ДжБосс разработчиков и серваков проблемы врядли будут, тут проблемы у __2 комманд которые делают систему модулей__ и при этом не удосужились принять достаточное участие в обсуждении.

но и в прикладных приложениях.

Я читал статью на которую вы дали ссылку, когда она вышла, и не увидел как бы это затронуло меня. Можете привести короткое саммари тех моментов (примеров), которые по вашему мнению, создадут реальные проблемы прикладным разработчикам? Уточнение: Необходимость потратить 1 день (или менее) на указание доп подулей при билде — это не проблема.

Никто не говорит, что Jigsaw — идеал, но с другой стороны это конкурент OSGi, тех же JBoss Modules, которые будут упираться до последнего.

Кстати, касательно Jersey. Я запускал достаточно простое самодельное приложение на spring, spring data jpa + hibernate, jersey на glassfish на 9ке. Все версии старые, т.е. без адаптации к 9ке. Все работало. Правда, скомпилено это было на 8ке, но запущено на 9ке.

У меня и собиралось и запускалось на JDK 9. Кстати, если Spring и Spring Data, то почему Jersey, а не например Spring MVC?

Субъективно jersey нравится больше, чем spring mvc restcontroller. Текущий рабочий проект на spring mvc, но я не могу сказать, что я в восторге. То, как те или иные кейсы обрабатываются в джерси, мне нравится больше.

Спасибо всем за комментарии.
Хочу добавить еще немного информации и своих мыслей, которые отсутствуют в статье.
JDK 9 и Jigsaw — это не только костыли. Как сейчас работают системы сборки Maven/Gradle? Они подготавливают classpath, который отдают java/javac. А дальше JVM нужно разбираться, что и как парсить и загружать. Теперь, при наличии module-info.java, будут сразу видны зависимости между модулями(jar-файлами), что позволит JVM оптимизировать и ускорить загрузку модулей.

Спасибо за обзор.
Кмк, основная суть проблем в сильной инкапсуляции классов внутри модулей jdk, которая запрещает доставать их по рефлекшену. То, что раньше можно было достать по имени и инстанциировать, сейчас либо невидимо, либо недоступно. И поэтому падают большинство либ и тулзов. Хотя конкретно по мавену я еще зимой читал что мавен вроде как заявил полную поддержку 9ки.

Спасибо. В девятой версии многое из того, что бралось через Reflection, теперь появилось в публичном API. Например, variable handles

А многое из того, что раньше было доступным, теперь недоступно. Или доступно, но только через api 9, т.е. опять таки отваливается обратная совместимость. Например, Class.forName, Class.getResource и Class.getResourceAsStream радикально поменяли поведения и больше не дадут достать класс по имени, если класс находится в именованном модуле, отличном от java.base.

А я так понимаю, что именно вот эти вот самые базовые вещи лежат в основе основ многих либ, работающих через рефлексию. И именно эту базу и сломали.

Да, но мне кажется, что это можно починить, добавив в либу module-info.java и указав там requires ... ?

Дело не в том, что это можно починить, указав какие-то костыли где-то в каких-то бексонечных настройках. Дело в том, что запакованное приложение больше не запускается, а должно.

Спасибо. Годная статья. Имхо, основная проблема девятки в том, что многие писаки либ — рукожопы. Из того что проверял — netty уже поддерживает 9-ку.

Спасибо. Есть одна вещь, которая на самом деле может подпортить жизнь разработчикам библиотек. В JDK запрещается помещать один и тот же пакет в разные модули. Сейчас же это случается постоянно. Посмотрим, как из этого положения будут выходить.

Це якийсь хелл %)
Я про костилі, стаття чудова :)

Что поделаешь, сколько лет Sun/Oracle мирились с тем, что в JDK был монолитным проектом, а разработчики освоили internal API лучше самих инженеров Oracle.

А как это Java9 ещё не вышла, а Epsilon GC внедрён во всём Львове?

Ok. Fuck Jigsaw compatibility. If doing so requires use of Java 9 tools, I’ll have zero-fucks level interesting in support for next 2+ years" © Tatu Saloranta
Hibernate пытается через reflection сделать публичным метод, который является protected или private.

Знали б вы сколько боевого софта через этот костыль работает. Когда библиотека чужая, не новейшей версии, править соответственно никто ничего не будет — а вот тем кто её пользует, костылить приходится.

И это вершина айсберга. А крупная проблема в том, что Java таки лишили обратной совместимости. Подобные казусы уже случались в малых масштабах, но даже тогда барьер был настолько катастрофой, что до сих пор есть корпоративные сервера что пашут под Java 4 и Java 5. Здесь похоже заимеем фобию девятки.

Серверам приложений как быть? Всё перекомпилить под девяточку вряд ли получится, тем более что проекту надо работать, зачастую круглосуточно. А значит единственно верное решение — ЗАПРЕТИТЬ писать новые аппликухи под девятку. Конечно, взойдут новые проекты, но как вы считаете, менеджеры снимут запрет для новых? Щщас. Они сделают ровно ничего, поскольку что-то разрешать менеджер имеет полномочия только в небольших компаниях.

Так что даже изучать не советую. Подождите когда про Java9 расскажут не на понтовых конференциях, а на StackOverflow.

хороший повод переходить на котлин :trollface:

И под jvm 9 будут те же проблемы

А вот это ж пля пистец. Одно дело когда софт серверный, другое — Java стоящая на клиентских компах. Результат предсказуем, как и у всего софта, стремящегося писать под «общие» библиотеки чтобы Dont-repeat-yourself. Каждый софт вынуждены будут поставлять со своей Java.

Притом для уже работающего софта придётся патчить систему. Не софт, а систему. И далеко не факт что юзер знает как, или даже имеет под это права.

На самом деле про девятку уже давно рассказывают на JavaOne/Devoxx. Некоторая сумятица в том, что спецификации постоянно обновлялись, поэтому нужно перепроверять информацию.

Прям руки опускаются пробовать девятку...
Спасибо за статью

Спасибо за статью и за проделаннаую работу. Познавательно.

ИМХО: че-то перехотелось переходить на 9ку, после прочтения статьи.

Тогда переходите на go. Там гарантируют обратную совместимость с предыдущими версиями — golang.org/doc/go1compat . В конце лета этого года должен выйти go 1.9 — почти java 9 :)

В джаве обратная совместимость тоже считалась краеугольным камнем.
Хотя лично мне кажется, что так и останется. Я думаю, что текущие проблемы 9ки пофиксят.

Очень толково и своевременно. Спасибо большое !

Подписаться на комментарии