Здравствуй, Хабр. Как ты помнишь, недавно произошёл официальный релиз Java 10. Учитывая, что практически все сейчас используют преимущественно 8-ку, с выходом 10-ки нас ждут такие вкусности как модульность (вошла в 9-ку) и local variable type inference. Звучит неплохо, можно попробовать поэкспериментировать с переносом какого-нибудь существующего проекта на 10-ку.
О том, какие разновидности боли ждут нас, можно узнать под катом.
Япросто оставлю это здесь не преследую цели изложить здесь все возможные проблемы с миграцией java-проектов с 8-й версии Java на 9-ю или 10-ю. Хочется зафиксировать свой небольшой опыт первого контакта с новыми версиями Java как-то кроме как в устных обсуждениях с коллегами. Возможно он кого-то остановит кому-то окажется полезен
Для начала давайте сделаем наше приложение модульным (не зря же разработчики JDK старались и страдали над проектом Jigsaw)?
Чтобы из существующей иерархии классов получить модуль java, нам потребуется:
Вроде бы выглядит всё довольно просто на первый взгляд.
Ничего просто не соберётся. Если вы хотите собрать модульно приложенье, то ваши зависимости также должны быть модульны. Иначе просто не получится добавить их в module-info.java. Точнее добавить-то можно — старомодные jar-файлы библиотек рассматриваются как так называемые «автоматические модули». Однако, проблема в том, что автоматические модули при сборке использовать нельзя. Да, в популярных репозиториях пока что очень мало библиотек, собраных в качестве модулей и вы просто не сможете их использовать. Nobody cares.
Здесь-то и понадобилась утилита jdeps, которая может проанализировать jar-файл и сгенерировать module-info.java для него, чтобы сделать из старомодного jar модуль. В качествекостыля workaround было решено просто взять и уйти распаковать все зависимости в одну кучку и сделать единый jar из которого можно попытаться сделать модный модуль-со-всеми-зависимостями. Пример на stackoverflow прилагается.
Jdeps разрешает зависимости рекурсивно, причём требует обязательного разрешения даже зависимостей, которые Maven/Gradle считают необязательными (optional). В итоге дерево зависимостей даже небольшого подопытного проекта разрастается в несколько раз. А модуль с зависимостями, который было решено изготовить в пункте (1.1.1)выкачивает пол интернета становится весьма тяжеловесным. Пример изложения сей боли здесь. Неожиданно обнаруживается, что log4j требует AWT, OSGI, ZeroMQ, Kafka или ещё что-нибудь в этом духе.
Обнаружилось, что в ситуации, когда некоторые классы дублируются в разных модулях, jdeps вываливается со стэктрэйсом IllegalStateException откуда-то из пучин своего кода без всякого пояснения что же пошло не так. Только одна маленькая зацепочка — сообщение «dependency on self» в качестве аргумента конструктора исключения. В какой-то момент мне показалось, что сие безобразие как бы намекало на то, что вообще всё (классы приложения, классы зависимостей, классы «необязательных» зависимостей) нужно свалить в один jar-файл. И действительно — сработало.
Даже когда всё уже чудесным образом собралось, модульно приложенье и даже custom образ JRE к нему, оказалось, что самые главные сюрпризы — ещё впереди. То есть в runtime.
В приложения Java зачастую есть необходимость определить путь к каталогу, гдесидит фазан лежит jar-файл, из которого произошёл запуск приложения (например jar-файл, содержащий точку входа). Это обычно нужно, чтобы понять, где искать файлы приложения — конфигурацию, плагины и т.п. Такие вещи, как правило лежат по соседству (что весьма логично). Для определения этого пути обычно помогает заклинание вида:
Каково же было моё удивление, когда я обнаружил, что в новых версиях Java это заклинание более не работает и я вижу вместо пути к файлу что-то вроде «jrt://com.example.myapp». Я начал лихорадочно гуглить и переполнять стэк. Пробовал определять модуль и как-то выяснить, какие файлы к нему относятся. Попытки обращения к classpath тоже привели меня ни к чему, так как модульно приложенье запускается с так называемым module path заместо старомодного classpath. Это был финиш. Именно в этот момент я решил сворачивать свой эксперимент по переезду на Java 10. Нет, конечно можно было бы путь к каталогу передавать в командной строке или принудить пользователя запускать только из правильной директории. Но я для себя лично решил, что с меня пока что хватит.
Оказалось, что вишенка на торте была не одна. Для экспериментов с десяткой я установил её и настроил в качестве JVM по умолчанию в системе. Поэтому сюрпризы продолжились. Код вроде
начал приводить к тому, что возвращался null в случае, если аргумент представлял из себя обычный CSV-файл с соответсвующим расширением. «Как же так?» — подумал я, «неужели JVM не в состоянии определить MIME-тип для обычного CSV-файла?». Я немного подебажил и обнаружил, что вызов вида
совершенно нагло кидает маловнятное исключение с сообщением вроде «malformed input».
Глубже в причинах этого безобразия я разбираться пока не стал. Лишь проверил файл /etc/mime.types —кракозябры не обнаружены вроде бы нормальный текстовый файл. То есть, JVM не смогла прочитать строки из текстового файла.
Начиная с версии 9 Java также предлагает новый механизм Service Providers взамен старого. Вещь неплохая, позволяет делать плагины, которые разрешаются в runtime. Новый механизм подразумевает объявление расширения в module-info.java с помощью ключевых слов «provides» и «with». Казалось бы, что может быть проще. На практике это не заработало, в отличие от старого механизма (использующего файлы resources/META-INF/services/...). Разбираться, в чём проблема времени не хватило. Однако отметку о том, что без специальных заклинаний не взлетает, я пожалуй, тут сделаю.
В случае с local variable type inference мы имеем новое ключевое слово «var» (variable, переменная), что намекает на влияние Scala. Однако лично мне показалось странным, почему не добавили также «val» (value, значение). Вместо этого приходится писать «final var», что читается глазами как «окончательная переменная» или даже как «постоянная переменная» — а это не только менее лаконично, но и как-то слегка противоречиво, на мой, привыкший к хорошему взгляд.
Также обнаружились довольно забавные вещи. Например:
Здесь JVM «запоминает» за именем «someStrings» тип ArrayList<String>. Иногда, приполной луне умной подсветке синтаксиса можно увидеть предупреждение, что тип константы избыточен, а в Java принято использовать в этом месте интерфейсы, например List<String> или даже Collection<String>. Этого предупреждения от шибко умного редактора можно избежать, объявив константу так:
но это уже выглядит как-то не очень лаконично. Этот момент не претендует на звание какого-то фатального недостатка, но демонстрирует лёгкий налёт противоречивости.
О том, какие разновидности боли ждут нас, можно узнать под катом.
Я
1. Проблемы с реализацией модульности
Для начала давайте сделаем наше приложение модульным (не зря же разработчики JDK старались и страдали над проектом Jigsaw)?
1.1. Проблемы с утилитой jdeps
Чтобы из существующей иерархии классов получить модуль java, нам потребуется:
- написать 1 или несколько файлов module-info.java с указанием в них имен модулей, зависимостей и экспортируемых пакетов
- добавить аргументы для компилятора (--module-path и прочие)
Вроде бы выглядит всё довольно просто на первый взгляд.
1.1.1. Зависимости от внешних библиотек больше не работают без специальных заклинаний
Ничего просто не соберётся. Если вы хотите собрать модульно приложенье, то ваши зависимости также должны быть модульны. Иначе просто не получится добавить их в module-info.java. Точнее добавить-то можно — старомодные jar-файлы библиотек рассматриваются как так называемые «автоматические модули». Однако, проблема в том, что автоматические модули при сборке использовать нельзя. Да, в популярных репозиториях пока что очень мало библиотек, собраных в качестве модулей и вы просто не сможете их использовать. Nobody cares.
Здесь-то и понадобилась утилита jdeps, которая может проанализировать jar-файл и сгенерировать module-info.java для него, чтобы сделать из старомодного jar модуль. В качестве
1.1.2. Потребуется включить значительно больше зависимостей, чем реально требуется
Jdeps разрешает зависимости рекурсивно, причём требует обязательного разрешения даже зависимостей, которые Maven/Gradle считают необязательными (optional). В итоге дерево зависимостей даже небольшого подопытного проекта разрастается в несколько раз. А модуль с зависимостями, который было решено изготовить в пункте (1.1.1)
1.1.3. Неожиданный публичный [Роскомнадзор] утилиты
Обнаружилось, что в ситуации, когда некоторые классы дублируются в разных модулях, jdeps вываливается со стэктрэйсом IllegalStateException откуда-то из пучин своего кода без всякого пояснения что же пошло не так. Только одна маленькая зацепочка — сообщение «dependency on self» в качестве аргумента конструктора исключения. В какой-то момент мне показалось, что сие безобразие как бы намекало на то, что вообще всё (классы приложения, классы зависимостей, классы «необязательных» зависимостей) нужно свалить в один jar-файл. И действительно — сработало.
1.2. Проблемы с runtime
Даже когда всё уже чудесным образом собралось, модульно приложенье и даже custom образ JRE к нему, оказалось, что самые главные сюрпризы — ещё впереди. То есть в runtime.
1.2.1. Теперь невозможно определить путь стартовому файлу/каталогу
В приложения Java зачастую есть необходимость определить путь к каталогу, где
Main.getProtectionDomain().getCodeSource().getLocation().toURI()
Каково же было моё удивление, когда я обнаружил, что в новых версиях Java это заклинание более не работает и я вижу вместо пути к файлу что-то вроде «jrt://com.example.myapp». Я начал лихорадочно гуглить и переполнять стэк. Пробовал определять модуль и как-то выяснить, какие файлы к нему относятся. Попытки обращения к classpath тоже привели меня ни к чему, так как модульно приложенье запускается с так называемым module path заместо старомодного classpath. Это был финиш. Именно в этот момент я решил сворачивать свой эксперимент по переезду на Java 10. Нет, конечно можно было бы путь к каталогу передавать в командной строке или принудить пользователя запускать только из правильной директории. Но я для себя лично решил, что с меня пока что хватит.
1.2.2. Провал при попытке определения MIME-типа файла.
Оказалось, что вишенка на торте была не одна. Для экспериментов с десяткой я установил её и настроил в качестве JVM по умолчанию в системе. Поэтому сюрпризы продолжились. Код вроде
final String mimeType = Files.probeContentType(itemInputFilePath);
начал приводить к тому, что возвращался null в случае, если аргумент представлял из себя обычный CSV-файл с соответсвующим расширением. «Как же так?» — подумал я, «неужели JVM не в состоянии определить MIME-тип для обычного CSV-файла?». Я немного подебажил и обнаружил, что вызов вида
Files.readAllLines("/etc/mime.types", Charset.defaultCharset())
совершенно нагло кидает маловнятное исключение с сообщением вроде «malformed input».
Глубже в причинах этого безобразия я разбираться пока не стал. Лишь проверил файл /etc/mime.types —
1.2.3. Нерабочий новый механизм Service Providers
Начиная с версии 9 Java также предлагает новый механизм Service Providers взамен старого. Вещь неплохая, позволяет делать плагины, которые разрешаются в runtime. Новый механизм подразумевает объявление расширения в module-info.java с помощью ключевых слов «provides» и «with». Казалось бы, что может быть проще. На практике это не заработало, в отличие от старого механизма (использующего файлы resources/META-INF/services/...). Разбираться, в чём проблема времени не хватило. Однако отметку о том, что без специальных заклинаний не взлетает, я пожалуй, тут сделаю.
2. Размышления о реализации local variable type inference
В случае с local variable type inference мы имеем новое ключевое слово «var» (variable, переменная), что намекает на влияние Scala. Однако лично мне показалось странным, почему не добавили также «val» (value, значение). Вместо этого приходится писать «final var», что читается глазами как «окончательная переменная» или даже как «постоянная переменная» — а это не только менее лаконично, но и как-то слегка противоречиво, на мой
Также обнаружились довольно забавные вещи. Например:
final var someStrings = new ArrayList<String>();
Здесь JVM «запоминает» за именем «someStrings» тип ArrayList<String>. Иногда, при
final var someStrings = (List<String>) new ArrayList<>();
но это уже выглядит как-то не очень лаконично. Этот момент не претендует на звание какого-то фатального недостатка, но демонстрирует лёгкий налёт противоречивости.
3. Неутешительные выводы
Некоторые из озвученных в этой записке проблем, возникли ещё в 9-й версии. Учитывая, что они так и не были пофиксеты до сих пор, есть неприятное ощущение, что весь этот бардак будет и в 11-й версии и даже далее. Не хотелось бы кликушествовать, но из этого субъективного ощущения вытекает другое: Java, как язык программирования, начинает переживать свой упадок. Возможно я не прав, а возможно так и есть и тогда на смену Java нужно искать другой инструмент.UPDATE
К сожалению, пишу по памяти, поэтому отсутствуют некоторые детали. Вспомнил также ещё одну замечательную вещь. Это анноташка sun.misc.Contended, которая появилась в 8-й версии и была благополучно выпилена в последующих. Несмотря на свою короткую жизнь она успела попасть в одну из используемых библиотек, что также доставило немало хлопот. Нет её в 10-ке и всё, делай что хочешь, а для сборки модуля — требуется, так как ссылка на класс есть. Кончилось всё тем, что я, помня, что библиотека conversantmedia disruptor не является обязательной зависимостью, я просто заменил требуемые классы из неё пустыми в своих исходниках. И это — одна из мелочей, которым не было числа…
Источник: https://habrahabr.ru/post/354114/?utm_source=habrahabr&utm_medium=rss&utm_campaign=354114
Смотри также:
Зачем нужна Java. http://fetisovvs.blogspot.com/2014/07/java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Spring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Блеск и нищета Java для настольных систем. http://fetisovvs.blogspot.com/2018/04/java-haulmont-java.htmlSpring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Разбор задачек от Одноклассников на JPoint 2018. http://fetisovvs.blogspot.com/2018/04/jpoint-2018-java.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Ускоряем время сборки и доставки java web приложения. http://fetisovvs.blogspot.com/2018/03/java-web-java.html
Комментариев нет:
Отправить комментарий