Проект Jigsaw в Java 9. Модулярное будущее, которого не избежать

в 13:47, , рубрики: java, joker, joker2016, jokerconf, Блог компании JUG.ru Group, Программирование, разработка

Проект Jigsaw в Java 9. Модулярное будущее, которого не избежать - 1

О проекте модуляризации Java мы слышим из года в год. Каждый раз ждём, следим за анонсами. Нам говорят, что Jigsaw практически готов, но каждый раз переносят срок выхода. Может быть, это не такой простой проект, как видится многим? Возможно, что изменения в нем повлияют на разработку в корне? Может быть, модулярная система — это только верхушка айсберга? Ответить на вопросы, связанные с проектом Jigsaw, мы попросили Ивана Крылова.

Проект Jigsaw в Java 9. Модулярное будущее, которого не избежать - 2 Иван занимается разработкой виртуальных машин Java и компиляторов более 10 лет, в настоящее время развивает компиляторную инфраструктуру в виртуальной машине Zing в Azul Systems. Регулярный докладчик на конференциях JUG.ru и других европейских Java-конференциях.

Что такое Jigsaw, и как он повлияет на мир Java?

— Иван, добрый день. Расскажите, пожалуйста, какое влияние проект Jigsaw окажет на экосистему Java?

— Добрый. Для объяснения моего взгляда на Java и модулярность стоит немного рассказать о моей деятельности. Коммерческим программированием я занимаюсь примерно двадцать лет. Последние одиннадцать из них — разработкой виртуальных машин. Работал в команде HotSpot Runtime в Sun. Потом компания была куплена Oracle. Далее было несколько изменений деятельности, и последние три с половиной года работаю в компании Azul Systems, где занимаюсь разработкой виртуальных машин.

Всё это я рассказал к тому, чтобы вы понимали, что мой взгляд на функциональность является взглядом разработчика JVM, человека, который достаточно далёк от мира «кровавого энтерпрайза», от проблем, с которыми сталкиваются разработчики высокого уровня абстракции.

Возвращаясь к вопросу о влиянии Jigsaw на экосистему Java, я бы сказал, что изначально это влияние не так велико, как может показаться. Начнём с того, что если разработчик не хочет переходить на парадигму модулей, он может не переходить. Старые способы взаимодействия с кодом и областями видимости в целом будут работать. Проблемы начнутся тогда, когда разработчик пытается перенести свой код в модули, а другой код еще не перенесён в модульную инфраструктуру.

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

— В Java 8 ключевыми нововведениями стали лямбда-выражения и Stream API. С какими значимыми изменениями можно сравнить Jigsaw?

— Модули проекта Jigsaw не оказывают большого влияния на написание кода и на функциональную последовательность действия кода. Jigsaw отвечает за то, как прописывается взаимодействие между различными компонентами, как мы заворачиваем код в единицы абстракции. В Java 9 и не планируются новые API, которые были бы революционными или сильно меняли то, как разработчики пишут свой код сегодня.

Есть определенные эволюционные изменения: например, в Java 8 появился Optional, в Java 9 в Optional добавится несколько методов, таких, как конвертер в Stream, метод or, но это инкрементальные изменения. Почувствовали необходимость добавить — добавили. Большие приятные изменения в Process API, которые позволят узнать pid виртуальной машины, получение дерева процессов, управления процессами. Process API позволит избавиться от некоторого количества хаков, которые нужно было наворачивать: например, вызовов shell с парсингом output или другие способы, уйдёт еще одна необходимость залезать в native. Такие инкрементальные изменения разбросаны по коду class libraries, но нет одного кода в API или одного мощного синтаксического изменения.

Все семантические изменений собраны в Milling Project Coin. Самое заметное — это, наверное, появление приватных (default) интерфейсных методов. Представим ситуацию, когда два приватных интерфейсных метода содержат общий код. Раньше такой код можно было вынести только в public метод, но такой метод может не иметь смысла с точки зрения публичного API, предлагаемого данным интерфейсом. Теперь такой метод может быть объявлен private. Этот метод не будет виден как часть API этого интерфейса, зато его реализацию можно спокойно использовать внутри этих дефолтных методов. Данное изменение позволит сделать чище тот API, который вы предлагаете с помощью интерфейса и, в частности, предлагая дефолтные реализациями методов этого интерфейса.

— Проект модуляризации существует давно, около девяти лет, сроки выхода переносились не один раз. На конференции JavaOne 2015 эта тема была удостоена вниманием нескольких докладчиков (Alan Bateman, Alex Buckley, Mark Reinhold). Такой ажиотаж вокруг показывает важность проекта?

— Проект Jigsaw потянул за собой определенные изменения. Я рассматриваю Jigsaw как некие две составные части, первая — это изменение в языке модели видимости, декларации модулей, синтаксис. Вторая часть — это то, как модель модулей была применена непосредственно к class libraries в jdk. Часть классов переехала, другие стали deprecated или перестали быть видимы снаружи.

Большие дебаты были про механизм reflection. Раньше он позволял достучаться до любого приватного класса и метода. Были возможны изменения полей на лету через reflection и их приватности. То есть были доступны, я бы сказал, эксплойты, которые использовались большим количеством библиотек. В конечном итоге это бы отразилось на пользователях. Перечисленные вами архитекторы языка вышли к сообществу, чтобы рассказать о том, какие изменения будут, почему они произойдут, зачем нужно делать эти болезненные изменения. Болезненность заключается в том, что некоторое количество библиотек сразу автоматически стало несовместимым. Перевод кода на более совместимую версию у них занимает время, что, как обычно, сопровождается шумом, например, таким: «переход на девятую версию ломает всё». Эти объяснения были необходимы.

Большое количество докладов про Jigsaw в прошлом году было на JavaOne, почти такое же количество на Devoxx в Антверпене. В этом будут такие же доклады, но будут интересные от сторонних людей, например, ребята из IBM рассказывают, есть ли жизнь после перехода на модули. Я с интересом буду ждать видеозаписей с JavaOne 2016.

— Новое понятие — модуль. Как правильно трактовать «составные» части модуля: module name, exports и requires? Хотелось бы поподробней остановиться на «requires» и понять, что же мы в действительности сможем «требовать», например, «requires local» или «requires optional»?

— Давайте начнем с простого книжного примера, ситуации, когда у вас нет существующего кода, вы просто пишете его с нуля. У вас есть классы и вы разместили их в разных пакетах, решили, что у них не должны быть тесной однозначной связи, и они в различных модулях. Когда какому-то классу из первого модуля нужны классы из второго модуля, он говорит, что мне нужен, то есть requires, вот этот модуль два, указывая какие пакету ему нужны. Вся иерархия достаточно строгая, если пакет управляет абстракциями на уровне классов, то модуль управляет иерархией уровня пакет, это выражение requires.

Проверка происходит в обоих случаях: и в момент компиляции и в момент исполнения, как это довольно часто происходит и с другими элементами языка Java. JVM не может гарантировать, что все проверки были пройдены, скажем так, честно, на уровне компиляции, байткод может приходить откуда угодно, поэтому во многом проверки дублируются и в javac, и на уровне JVM Runtime. Requires проверяет, что необходимые для работы этого модуля пакеты доступны, эти пакеты находятся каком то модуле два или модуле три. Если тем, в свою очередь, нужны ещё какие-то модули, то строится такая транзитивная цепочка с тем, чтобы проверить буквально в самом начале работы, что все необходимые пакеты из соответствующих модулей нам доступны.

Затем, exports — это механизм видимости. Он говорит о том, что этот модуль предоставляет такие-то пакеты другим модулям. Можно указать, что всем сразу — это условная "*", которая показывает, что все другие модули, все, кто хочет пользоваться этим модулем, могут пользоваться пакетами. Это означает не то, что абсолютно все классы доступны пользователям, а лишь те, которые задекларированы public, т.е. те, которые ескейпятся, становятся доступны другим модулям через этот механизм. Можно указать export to когда вы хотите проэкспортировать непосредственно указанному модулю и никому больше и этот механизм статической декларации.

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

В принципе сама jdk перестроена таким образом, чтобы весь набор библиотек jdk представлял набор модулей с понятными зависимостями. Результат такой, что мы можем построить некий subset jre, в которой будут только те модули, которые нам необходимы. В этой связи появилась утилита jlink, которая позволяет собрать Run-Time Image только лишь из тех модулей, которые нам нужны. Это определенное развитие идеи, которая появилась в Java 8 с профилями. В jdk 8 можно было собрать jdk под разные профили с разным количеством классов и разным количеством функциональности, но это, скажем так, статическая сборка, без проверки того, что уже реально приложению понадобится. А jlink позволит нам собрать некий переносимый Runtime, который будет состоять из виртуальной машины, модуля java.base, еще какого то количества модулей, необходимых приложению.

В одной презентации авторы книги про модули в java 9 Paul Bakker и Sander Mak приводили различные примеры. Собрали вариант программы простого текстового анализатора с необходимым рантаймом — получился дистрибутив примерно в 20 мегабайт. Когда добавили в приложение swing-окошки, то добавились новые модули, размер такого дистрибутива вырос до 50 мегабайт. Границы JDK библиотек при этом размазываются, потому что помимо модулей, которые находятся внутри jdk и которые будут поставляться вместе с jdk, также можно добавлять модули сторонних библиотек, т.е. такие дистрибутивы совершенно по разному будут скомпонованы. Это позволит по новому распространить программу, не требуется чтобы заказчик пошел и скачал новую версию jdk, а поставлять непосредственно собранным Runtime`ом.

— О проблеме "Dependency hell" можно забыть навсегда?

— Довольно часто в реальных продакшн-системах можно увидеть очень длинную строчку classpath, где перечислено большое количество jar, или каталогов, где находится скомпилированный код. Зачастую оказывается так, что в классах задекларированы одни и те же классы одних и тех же пакетов, и тогда Runtime берет тот, который раньше появился в classpath. В связи с этим очень много людей отмечали такую проблему, когда от перемены мест jar в class-пути менялось поведение программы, вот это с помощью модулей решается на корню.

Происходит это следующим образом: если у нас классы разложены по пакетам, то определённый пакет может декларировать один-единственный модуль. Поэтому если, например, у нас находятся два модуля в двух jar и оба они включают элементы пакета, то такая ситуация невозможна, потому что предполагается, что теперь пакет принадлежит одному из модулей, т.к. каждый модуль декларирует пакет однозначно. При этом Jigsaw не решает проблему версионного контроля, т.е. соответствие версий в том понимании, в которым мы привыкли видеть в package manager-ах, например, в Linux.

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

— Появятся различные виды модулей: Named Modules, The Unnamed Module, Automatic Modules. Вы можете кратко рассказать о них? В чем состоят их отличия и что каждый привносит в общую структуру?

— Если вы пишите модуль «с нуля», по всем правилам, то есть положите его в каталог верхнего уровня с названием модуля, добавите файл module-info.java, в котором содержится декларация модуля, название, которое должно совпадать с названием каталога, с перечислением exports и requires. Такой модуль — это обычный именованный модуль.

Если вы живете по старинке и у вас есть классы, которые вы просто привыкли закидывать через classpath, они тоже окажутся в модуле, который называется Unnamed Module. Поэтому для модулей, которое вы не назвали, есть безымянное последнее прибежище, так как теперь девятка не сможет работать без модулей.

Есть некая промежуточная стадия, когда у вас, например, jar файл, в нем находятся какие то пакеты, вы его положили не в classpath, а в modulepath, тогда Runtime начинает представлять его себе этот файл как некий модуль, название которого совпадает с названием jar-файла, и с ним дальше можно делать похожие вещи, как с Named модулем, и ссылаться на него.

Например, если вы разрабатываете свой именованный модуль. Вы зависите от какой то библиотеки, которая ещё не была переведена в модульную структуру, то сможете положить jar с этой библиотекой в modulepath. Он станет автоматическим модулем. Теперь вы в module-info.java можете ссылаться: requires и дальше имя модуля, который был назначен ему автоматически.

И наконец, последнее изменение, которое происходит последние недели, и ещё не совсем понятно, будет ли принят это proposal или нет. Я уже сказал чуть раньше, что существует определенная проблема с reflection, если раньше можно было достучаться до чего угодно, то теперь на уровне виртуальной машины существует защита, и появляется новая концепция, которая сейчас идёт в разработке, и, возможно, попадет в стандарт, хотя это не гарантировано — weak модуль.

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

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

OSGi vs Jigsaw: холивара не будет

— Почему не была одобрена OSGi для модуляризации Java? Вы согласны со словами Mark Reinhold: «The OSGi module layer is not operative at compile time; it only addresses modularity during packaging, deployment, and execution. As it stands, moreover, it’s useful for library and application modules but, since it’s built strictly on top of the Java SE Platform, it can’t be used to modularize the Platform itself»? Существует проект Penrose для взаимодействия OSGi/Jigsaw. (Модульный слой OSGi не действует в момент компиляции; обращается к модулярности только во время архивации, развёртывания или выполнения. Кроме того, в том виде, в каком он (OSGi) есть, он полезен для библиотек и модулей приложений, но поскольку он создаётся строго на основе Java SE Platform и не может быть использован для модуляризации своей платформы (прим. перевод автора).

— Я специально изучил OSGi, и у меня появилось определённое мнение на счёт того, почему всё-таки не OSGi стал Jigsaw. Первый ответ на поверхности, его обозначил Mark Reinhold: сам механизм написан поверх спецификации Java SE, если мы хотим делать поставку Java SE фрагментами, то есть модулями, то механизм OSGi нам не поможет, нам необходимо что-то на более низком уровне. В целом OSGi работает через механизм class loading`а, то есть появляется сначала один общий OSGi environment classloader, потом на каждый bundle OSGi появляется по своему classloader'у, и благодаря этому происходит некое разграничение видимости классов.

Механизм Jigsaw делает аналогичную функциональность, но уже на уровне виртуальной машины, что, в свою очередь, достаточно существенно для производительности самой виртуальной машины. Не секрет, что внутри виртуальной машины класс адресуется через последовательность: classloader и дальше пакет и имя класса, через вот эту пару. И если пакета и имя класса — это конкретные строковые литералы, которые ему соответствует, то classloader — это просто референс на объект class loading'a. Таким образом проанализировать в случае persist'a, когда мы завершили виртуальную машину и потом эти данные пытаемся восстановить при следующей загрузки, этот референс теряет какой либо смысл, у classloader'а нет собственного строкового литерала. Это накладывает ограничения на оптимизацию работы вот в таком ahead of time стиле. Поэтому разработчики Jigsaw решили отказаться от базирования этого механизма видимости на механизм class loading'a, как это было сделано в OSGi. В OSGi так сделали, потому что это было единственное, что им доступно из того, что предоставляет виртуальная машина, они не могли вмешаться в механизм class resolution.

— Получается, что фактически реализация у OSGi и Jigsaw на разных уровнях.

— Да, совершенно верно. Jigsaw реализован на более низком уровне. К счастью или несчастью. Наверное, к некоторому несчастью для тех, кто уже успел задействовать OSGi. Теперь придётся как-то взаимодействовать, поддерживать два параллельных описания зависимостей, или использовать проект типа Penrose, который позволяет синхронизировать. Описание задаётся с помощью JSON-скрипта, из которого можно сгенерировать модульное описание, Jigsaw-описание зависимостей и OSGi-описание. Придётся скрещивать эти два мира, и у тех пользователей, которые используют OSGi в настоящее время, жизнь станет немножко сложнее.

— Какие изменения произойдут в работе classloader'ов?

— Изменения на самом деле не такие уж и большие. Раньше у нас был механизм, состоящий из трех ступеней, это Bootstrap classloader, Extension classloader и Application classloader, который раньше был instance of URL classloader. Изменения в Java 9 состоит в том, что Extension classloader преобразуется в Platform classloader и связано это с тем, что если раньше Extension использовался для загрузки определённого вида классов, дополнений к jdk, их было немного. В Java 9 провели определённую работу и поняли, что у большого количества модулей можно понизить привилегию, чтобы перенести загрузку из Bootstrap classloader, и делать это с помощью Platform classloader. Наконец, Application classloader, если раньше он был instance of URL classloader, теперь это instance некого внутреннего класса. Это, в частности, в определённый момент сломало Gradle, который использовал это знание, он искал instance of URL classloader — это было неправильно. Вроде как Gradle уже поддерживает Jigsaw и эта проблема там решена.

С точки зрения пользователя, если приложение не использует эти знания, представления, как это делал Gradle, ничего сильно не изменится. В противном случае необходимо будет пересмотреть механизм, как вы работаете с Application classloader'ом. Для большинства людей эти изменения пройдут незаметно.

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

Область, которая получает развитие — это дополнительные оптимизации, которые связаны с тем, что если класс не выходит за пределы модуля, т.е. он не экспортируется через пакет наружу, у нас появляется возможность делать Whole Program Optimization, которые раньше были недоступны, т.к. раньше класс мог появиться в неожиданный момент, целый пласт jit-оптимизации был нам недоступен. Вплоть до того, что есть определённая информация, что Oracle работает над интегрированием ahead of time компиляции и упаковки уже бинарно скомпилированного кода в jmod, это ещё один формат для хранения модулей для более быстрого старта кода, который заключён в эти модули. Но по этому поводу информации я видел пока очень мало, посмотрим, возможно, что работа еще не доведена до какого то момента.

Решаем головоломку: ошибки, предупреждения и предостережения

— Какое мнение у вас сложилось о системе предупреждений и ошибок во время компиляции?

— Не могу сказать, что у меня пока огромный опыт в разработке Jigsaw модулей, я попробовал различные примеры. Могу рекомендовать читателям взять хрестоматийный вариант с двумя модулями, где один экспортирует, а второй что-то requires. Далее попробовать скомпилировать и проверить, что всё работает, получить, например, «Hello World». А потом стоит взять и поменять что-то: убрать requires или exports. Поменять название модуля, чтобы он не совпадал с названием каталога в котором он лежит, поменять название на кириллицу, вставить символ в название, например, подчёркивание или цифры и посмотреть, что случится.

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

По моим представлениям ошибки фазы компиляции будут сродни таковым, когда вы пытаетесь достучаться внутри пакета к private классу откуда-то, у вас ошибка видимости. Такую диагностику javac компилятор делает для модулей, когда вы пытаетесь достучаться до класса, который не видим. В части диагностики я не вижу больших изменений, правила, конечно же, меняются, потому что поменялись правила видимости и class resolution, я не думаю, что будет сильная разница с ошибками, которые встречаются при ошибках видимости, связанные с пакетами. Т.е. это ещё один уровень абстракции, но категория ошибок точно такая же.

— В основной массе, IDE уже имеют возможность выбора языкового уровня «Jigsaw». Возможно, что часть проверок и контроля будет сделана за пользователя. Что вы думаете по этому поводу?

— Самая большая связанная с модулями сложность, как мне кажется, заключается в том, что проект идёт очень долго, само слово module очень многозначно, и попытки найти ответы на StackOverflow или в Google зачастую приводят на устаревшие тексты, поэтому очень внимательно нужно смотреть на дату ответов.

Аналогично с IDE: например, полтора года назад я открыл IDEA, и там уже встречалось слово «модуль», но подразумевались другие модули, не имеющие отношение к Jigsaw. Поэтому, если вы планируете разработку модулей в IDE, то нужно убедиться, что IDE свежая, т.к. можно нарваться на другое прочтение слова «модуль».

— Одно из определений Jigsaw — «головоломка». Насколько сложной стала разработка, на ваш взгляд?

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

В Java 8 появилась утилита jdep, она входит в стандартную поставку jdk, которая позволяет посмотреть на зависимости между пакетами и в Java 9 появляется возможность натравить вот эту утилиту на jar файл и сгенерировать module-info.java в файл, так что на него можно посмотреть глазами, подредактировать и увидеть, что какие то requires нам на самом деле не нужны или их можно заменить. Jdep дает полностью готовый module-info.java, и можно посмотреть и привести в такое состояние, в каком вы хотите его видеть, т.е. не нужно писать с нуля, анализировать все свои зависимости, jdep прекрасно с этим справляется и очень сильно в этом помогает.

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


О модулярности Java 9 на Joker 2016 расскажет Sander Mak, который сейчас готовит книгу Java 9 Modularity для O'Reilly, в своём докладе Java 9 Modularity in Action.

Если вы любите «кишочки» JVM и ждете Java 9 так же, как мы, то рекомендуем вам посмотреть следующие доклады Joker 2016:

Полная программа конференции – на сайте.

Автор: JUG.ru Group

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js