Модули
При компиляции указывается путь к зависимостям (модулям) посредством флага -p или --module-path:
1 | javac -p modules-directory/ext -d output src/my/package/*.java src/module-info.java |
Запуск класса модуля
Для этого используется путь к модулям, а так же указание - какой класс, в каком модуле запускать:
1 | java --module-path modules-directory/ext --module some.module/some.class.InModule |
Так запустится класс InModule внутри пакета some.class в составе модуля some.module. Для --module существует короткая форма, -m (как и для директории с модулями).
1 | java -p modules-directory/ext -m some.module/some.class.InModule |
Установка в jar
Используется стандартная установка:
1 | jar -cvf mods/some.module.first.jar -c modules-directory/ext |
Открытие модуля извне
Для этого используется дополнительная запись в module-info.java - файле, объявляющем модуль:
1 | module ${module-name} { |
Файл должен располагаться в корне модуля (в корне jar).
Использование стороннего модуля
Чтобы использовать сторонний модуль, необходимо его прописать в спецификации модуля - module-info.java.
1 | module ${module-name} { |
Module-info
При директиве exports можно указывать не только пакеты целиком, но и классы выборочно.
Так же, экспорт пакетов можно ограничить для одного или нескольких модулей. Для этого в конец директивы exports следует добавить наименование тех модулей, котором следует предоставить доступ к экспортируемому пакету (или классу).
Области видимости java типов и переменных для других модулей будут работать так же, как и для единого проектного кода. Для private и package-private, правда, доступ закрыт полностью (в т.ч. для и наследников).
requires transitive
Данное “улучшение” обязательности модуля выражаются в том, что все зависимые модули так же получат transitive зависимости:

Это позволяет сократить количество лишних упоминаний requires в спецификациях module-info.
Описание модуля
Описание можно получить посредством флага --describe-module (допускается использование не только java, но и jar):
1 | java -p mods-dir --describe-module my.module.first |
В нем может содержаться подобная строка:
1 | requires java\.base mandated |
Модуль java.base - это стандартный набор java пакетов, который не требует import классов. Например, String, Integer и т.д.
Список доступных модулей
Используется команда java с флагом --list-modules:
1 | java.base@11.0.2 |
При этом, если указать директорию с модулями будет выдана инфо и по ним:
1 | java -p mod-dir --list-modules |
Выведены будут все доступные по умолчанию (как в примере ранее), а так же модули, которые расположены в соответствующей директории с модулями. Путь к jar файлам модулей будет так же указан в описании (чего нельзя сказать про стандартные библиотеки).
Утилита jdeps
Используется для анализа зависимостей модулей. Позволяет просмотреть зависимости модуля. Вывод утилиты может быть, например, таким:
1 | my.package.jar -> java.base |
Данный вывод включает описание тех библиотек, которые используются. Если данная информация избыточна, можно добавить флаг -s, сокращающий вывод.
Использование модуля jdk.unsupported лучше избегать, поскольку это может привести к нарушениям работы при переходе на новые версии Java. Чтобы было проще отловить такие случаи, предусмотрен флаг --jdk-internals:
1 | my.pack.jar -> jdk.unsupported |
Утилита jmod
Позволяет работать с нативным кодом и библиотеками в связке с модулями. Оперирует с JMOD файлами.
Основные директивы:
exports {package}- открывает доступ к пакету;exports {package} to {module}- открывает доступ к пакету для модуля;requires {module}- показывает зависимость модуля от другого модуля;requires transitive {module}- добавляет сквозную зависимость;uses {interface};provides {interface} with {class};
Именованный (named) модуль
Сторонний модуль, используемый в приложении или частный модуль, созданный разработчиком. Должен включать module-info.java.
Должен присутствовать в module-path (указывается как аргумент java или jar команды).
Automatic-модуль
По умолчанию, Java может распознать любой jar, находящийся в module-path. При этом, существуют следующие шаблоны именования: {module-name}-{module-version}.jar. Для {module-version} допускается использование точки (“.“) в качестве разделителя.
{module-name} является именем модуля. При этом, любой специальный символ кроме “.“ будет преобразован в “.“. Специальные символы в начале и в конце имени будут удалены. Итак, алгоритм именования следующий:
- Поиск
META-INF/MANIFEST.MF. Если он найден, выполняется получение из негоAutomatic-Module-Name. Если нет - выполняются действия согласно алгоритму ниже; - Отбрасывается расширение файла (
.jar); - Удаляется версия, типа
-1.14.2,-4.2.0; - Удаляются все специальные символы с обеих сторон имени модуля;
- Все специальные символы конвертируются в “.“;
1 | commons-abs-1.0.0.jar -> name: commons.abc / version: 1.0.0 |
Неименованный (unnamed) модуль
Расположен в classpath. Даже если содержит в себе module-info.java, он будет проигнорирован!
Сравнение различных видов модулей
- Named
- Содержит
module-info.java; - Помещен в
module-path; - Экспортируется только в те модули, которые перечислены в
module-info; - Публичен для библиотек в
classpath;
- Содержит
- Automatic
- Помещен в
module-path; - Виден всем модулям;
- Публичен для библиотек в
classpath;
- Помещен в
- Unnamed
- Помещен в
classpath; - Виден всем модулям;
- Публичен для библиотек в
classpath; - Даже если содержит
module-info.java, будет проигнорирован;
- Помещен в
Стандартные библиотеки и модули
Модульная реализация всегда, для любого модуля включает в себя java.base. Данный модуль относится к стандартным, поставляемым с любой JDK. Ниже представлены дополнительные стандартные модули, которые требуют подключения:
java.desktop- пакеты AWT и Swing;java.logging- пакеты для LoggingAPI;java.sql- все для работы с JDBC;java.xml- все для работы с XML;java.*- другие пакеты, доступные для разработчика и которые могут ему понадобиться для импорта библиотек и классов;jdk.*- включает библиотеки для более глубокой разработки (например, для кастомизации компилятора, отладки, управления JDK);jdk.net;jdk.xml.dom;jdk.httpserver;jdk.charset;java.compiler- включает языковую модель, API для доступа к компилятору, обработку аннотаций;java.datatransfer- модуль, предназначенный для транспортировки данных в приложение и между ними;java.desktop- утилитарные классы для desktop и swing приложений;java.instrument- содержит сервисы, помогающие приложениям в работе на JVM;java.logging- классы для журналирования (логгирования);java.management- модуль для мониторинга и управления Java машиной;java.management.rmi- позволяет использовать JMX для подключения клиентов по протоколу RMI;java.naming- позволяет использовать JNDI (Java Naming and Directory Interface);java.net.http- подключает стандартный HTTP клиент и API для работы с web-сокетами;java.prefs- открывает доступ к пользовательским и системным настройкам и конфигурационным данным;java.rmi;java.scripting- предоставляет доступ к скриптам через командную строку,jrunscript, который допускает выполнение JavaScript и подобных скриптовых языков;java.se- стандартное API для Java SE;java.security.jgss- подключает зависимости для работы с IETF Generic Security Services API (GSS-API), включая Kerberos 5 и SPNEGO;java.security.sasl- подключает зависимости для работы с IETF Simple Authentication and Security Layer (SASL), включая механизм DIGEST-MD5, CRAM-MD5 и NTLM;java.smartcardio- открывает доступ к Smart Card I/O API для чтения и записи данных на смарт-карты;java.sql- работа с SQL;java.sql.rowset- работа с SQL записями, является частью java.sql;java.transaction.xa- разрешает доступ к API распределенных транзакций в JDBC;java.xml- классы для работы с XML документами;java.xml.crypto- классы для работы с криптографией в XML документах.
Миграция и модульность
Bottom-Up (миграция снизу вверх). Самая простая стратегия миграции:
- Выбор цели, которая содержит минимальное количество зависимостей (в особенности, от нестандартных модулей);
- Добавление
module-info.javaс указаниемexportsна те пакеты, которые следует открыть другим модулям; - Перенос модуля в
module-path; - Повторение действий 1-3 для всех вышестоящих библиотек (модулей);
- Провести обзор можно ли добавить к
requiresдирективуtransitive. Лишниеrequiresтогда можно убрать.
Top-Down (миграция свеху вниз). Эффективна тогда, когда нет возможности изменить каждый имеющийся модуль (jar):
- Поместить все
jarвmodule-path - Для наиболее зависимых модулей добавлять
module-info.javaтуда, где это возможно. Если такой возможности нет - модуль следует просто проигнорировать. - Двигаясь от наиболее зависимых к менее зависимым, выполнять пункты 1-2
Разбиение монолита на модули
Первым делом проводится декомпозиция и определяются связи между пакетами.
В случае возникновения цикличных зависимостей, стандартным решением является выделение дополнительного модуля, который может содержать только интерфейсную часть (если не удастся включить реализацию).
В случае обнаружения цикличных зависимостей, компилятор укажет на данную проблему:
1 | myPackModule/module-info.java:4: error: cyclic dependence involving my.pack.cycle.mod requires my.pack.cycle.mod |
Важно отметить, что импорт пакетов циклично между модулями не запрещен (как и в рамках одного модуля)!
Сервисы
Для создания сервиса в модуле необходимо:
- В
module-info.javaтого модуля, в котором предполагается использовать некий сервис, указать директивуusesс именем сервиса:
1 | // module-info.java |
- В импортируемом модуле создать сервис (обычный POJO, который может быть интерфейсом):
1 | package my.package.service.api; |
- В модуле, который использует сервис, необходимо найти его среди многих:
1 | ServiceLoader<MyService> svc = ServiceLoader.load(MyService.class); |
- В каком-то из модулей добавить реализацию сервиса и указать его в
module-info.java:
1 | module my.package.service.api.simpleimpl { |
В одном модуле допускается ТОЛЬКО одна реализация сервиса. Если необходимо наличие нескольких реализаций - придется создавать дополнительные модули.
- Все модули с реализацией поместить в
module-path. Java найдет их самостоятельно.
Директива uses обязательна в module-info.java если в коде будет вызов ServiceLoader.load. Даже если сам модуль имеет в себе реализацию запрашиваемого сервиса. Без этой директивы поиск будет безрезультатным (хотя фактически, сам модуль и содержит реализацию).
jlink
Собирает приложение, состоящее из нескольких модулей, определяя между ними зависимоти и собирая готовый продукт, доступный к запуску без необходимости наличия предустановленной JRE.
jlink --module-path ${path_to_modules_dir} --add-module ${root_module_name} -- output ${output_directory}