Статьи

Модульная система Java: первый взгляд

Модульная система для Java появилась очень давно. В конце 2014 года для этого был создан новый документ с требованиями JSR (JSR-376). Изменения запланированы для Java 9. Однако, никакого рабочего прототипа не было. До вчерашнего дня. Теперь есть ранняя сборка OpenJDK, включающая в себя Project Jigsaw.

Вместе со своим коллегой Полом Баккером я вчера выступил с докладом о предлагаемой системе Java Module на JavaZone. Мы должны были основывать это исключительно на документе с требованиями JSR-376 и на других тонкостях информации, распространяющейся вокруг. Предлагая этот доклад в начале года, мы определенно думали, что прототип будет доступен для демонстрации. Однако это не совсем так, как мы думали. Вместо этого прототип был выпущен через несколько часов после окончания нашего разговора (фу). Это означает, что некоторые вещи, которые мы говорим в разговоре, уже устарели, но основные идеи остаются в силе. Если вы совершенно новичок в предложении системы Java Module, я рекомендую вам посмотреть наш доклад, прежде чем читать дальше. Он объясняет текущее предложение и помещает его в более широкий контекст, сравнивая его с OSGi.

Почему Модули?

Так что же такое модули и зачем мы их хотим? Если вы хотите углубленное обсуждение, прочитайте «Состояние модульной системы» или посмотрите наш доклад . Для непосвященных, вот версия заметок Клиффа.

У Java есть фляги. Но на самом деле это просто прославленные zip-файлы, содержащие классы, которые, в свою очередь, находятся внутри пакетов. Когда вы собираете и запускаете приложение, состоящее из разных jar-файлов (читай: каждое нетривиальное приложение), вы помещаете их в путь к классам. И тогда надейся на лучшее. Потому что невозможно определить, поместили ли вы в путь к классам все, что нужно вашему приложению. Или вы случайно поместили одни и те же классы (в разные банки) в путь к классам. Classpath-ад (аналог DLL-ад) реальная вещь. Это приводит к плохим ситуациям, воспитывающим свою уродливую голову во время выполнения. Кроме того, знание того, что класс находился в jar-файле, теряется во время выполнения. JRE видит только одну большую коллекцию классов. Но банкам нужны другие банки. Это просто не закодировано явно в любой форме метаданных на данный момент. В идеале,вы также сможете скрыть классы реализации внутри вашего jar и выставить только свой публичный API. Предлагаемая модульная система для Java направлена ​​на решение этих проблем:

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

Наличие такой модульной системы значительно повышает удобство обслуживания, надежность и безопасность больших систем. Ни в малейшей степени из самого JDK. При наличии такой системы граф модулей может быть построен автоматически. Этот график содержит только необходимые модули для запуска вашего приложения.

Установка JDK9 Ранний доступ

Если вы хотите следовать примеру кода самостоятельно, вам необходимо установить раннюю сборку доступа JDK9, включающую прототип Jigsaw. В OSX это означает извлечение архива и перемещение извлеченного каталога в /Library/Java/JavaVirtualMachines/. Затем вам нужно настроить путь и JAVA_HOMEпеременную среды так, чтобы они указывали на каталог JDK9. Я использую отличный скрипт setjdk bash для переключения между установками Java в командной строке. Вы наверняка не хотите использовать эту раннюю сборку доступа в качестве ежедневной установки Java. Вы можете проверить, что установка работает, выполнив java -version. Вывод должен выглядеть примерно так:

java version "1.9.0-ea"
Java(TM) SE Runtime Environment (build 1.9.0-ea-jigsaw-nightly-h3337-20150908-b80)
Java HotSpot(TM) 64-Bit Server VM (build 1.9.0-ea-jigsaw-nightly-h3337-20150908-b80, mixed mode)

Пока это включает в себя фразу Jigsaw, вы можете идти. Полученный в результате код для примера можно найти по адресу https://github.com/sandermak/jigsaw-firstlook .

Маленький пример

Вы все еще можете использовать JDK9 в «унаследованном режиме» только с классами, банками и путем к классам. Но очевидно, что мы хотим работать с модулями. Итак, мы создадим проект, который производит два модуля, где module1 использует код из module2.

Первое, что нужно сделать, это структурировать ваш проект так, чтобы модули были четко разделены. Затем метаданные необходимо добавить в модули в виде module-info.javaфайла. Наш пример построен следующим образом:

src\
  module1\
     module-info.java
     com\test\TestClassModule1.java
  module2\
     module-info.java
     com\moretest\TestClassModule2.java

По сути, это представляет другой уровень (module1, module2) поверх уровня пакетов, который вы уже делаете в Java. В этих «каталогах модулей» мы находим module-info.javaдескриптор в корне. Кроме того, обратите внимание, что два класса находятся в пакетах с разными именами.

Давайте посмотрим на код для TestClassModule1:

package com.test;

import com.moretest.TestClassModule2;

public class TestClassModule1 {
   public static void main(String[] args) {
     System.out.println("Hi from " + TestClassModule2.msg());
   }
}

Выглядит довольно ванильно, верно? Ничто не имеет отношения к модулям, происходящим здесь. Существует импорт для TestClassModule2, по которому метод main позже вызывает msg()метод:

package com.moretest;

public class TestClassModule2 {
   public static String msg() {
     return "from module 2!";
   }
}

Сейчас мы оставим module-info.javaфайлы пустыми.

Компиляция Java-модулей

Теперь для следующего шага: фактически скомпилировать наши модули и связанные исходные файлы. Чтобы сделать это, введен новый флаг компилятора javac:

javac -modulesourcepath src -d mods $(find src -name '*.java')

Это предполагает, что вы запускаете команду из родительского каталога каталога src. Флаг -modulesourcepath переключает javac в режим модуля, а не в «старый» режим. Флаг -d указывает выходной каталог для скомпилированных модулей. Они выводятся javac в разобранном формате каталога. Если позже мы захотим поставить модули в виде jar-файлов, это отдельный шаг.

Так что же произойдет, если мы выполним вышеуказанный вызов javac? Мы получаем ошибки!

src/module1/module-info.java:1: error: expected 'module'
src/module2/module-info.java:1: error: expected 'module'

Пустые module-info.javaфайлы наносят ущерб здесь. Для этих файлов введены новые ключевые слова, наиболее важным из которых является module. Эти новые ключевые слова относятся к определению module-info.java. Вы все еще можете использовать переменные, вызываемые moduleв других исходных файлах Java.

Мы обновляем дескрипторы модуля, чтобы они содержали минимальное количество необходимой информации:

module module1 { }

и для модуля 2:

module module2 { }

Теперь модули имеют явное имя в своих определениях, но пока не содержат никаких других метаданных. Компиляция снова приводит к новым ошибкам:

src/module1/com/test/TestClassModule1.java:3: error: TestClassModule2 is not visible because package com.moretest is not visible

Инкапсуляция в действии! По умолчанию все классы / типы внутри модуля скрыты от внешнего мира. Вот почему javac запрещает использование TestClassModule2, хотя это публичный класс. Если бы мы все еще были в плоском мире классов, все было бы хорошо и модно. Конечно, мы можем исправить это, явно воздействуя TestClassModule2на внешний мир. Следующие изменения необходимы в module2 module-info.java:

module module2 {
  exports com.moretest;
}

Этого не достаточно. Если вы скомпилируете это изменение, вы все равно получите ту же ошибку. Это потому, что module2 теперь предоставляет правильный пакет (и, следовательно, все, что он содержит открытые типы), но module1 еще не выражает свою зависимость от module2. Мы можем сделать это, изменив module1 module-info.java:

module module1 {
   requires module2;
}

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

Запуск модульного кода

Конечно, просто компилировать не очень весело. Мы также хотим, чтобы приложение работало. К счастью, в этом прототипе JRE и JDK также были осведомлены о модулях. Приложение можно запустить, определив путь к модулю, а не путь к классам:

java -mp mods -m module1/com.test.TestClassModule1

Мы указываем путь к модулю в директорию, в modsкоторую написал javac. Затем -m используется для обозначения исходного модуля, который запускает разрешение графа модуля. Мы также добавляем имя основного класса, который должен быть вызван, и вот оно:

Hi from from module 2!

Будущее

Этот первый взгляд дает представление о том, что вы можете сделать с модулями в Java 9. Здесь есть еще много чего, чтобы исследовать здесь. Как и в случае с упаковкой: помимо jars, появился новый формат, названный jmod. Система модулей также включает уровень услуг, который может связывать поставщиков услуг и потребителей через интерфейсы. Думайте об этом как об инверсии контроля, когда система модулей выполняет роль реестра служб. Также очень интересно посмотреть, как модульная система использовалась для модульной работы самого JDK . Это, в свою очередь, позволяет создавать приятные вещи, такие как создание образа во время выполнения, который содержит только JDK и модули приложения, которые нужны вашему приложению, и ничего более. Меньше занимаемая площадь, больше возможностей для оптимизации всей программы и т. Д. Все это очень многообещающе.

Следующий шаг для меня — попытаться портировать пример приложения OSGi, использующего несколько модулей и сервисов, на систему модулей Java 9. Будьте на связи!