W3docs

Типы модулей Java

Именованные, автоматические и безымянные модули в Java и их взаимодействие при компиляции и выполнении.

Java Platform Module System (JPMS) различает три вида модулей. Только один из них — «настоящий», который вы создаёте сами; два других существуют для того, чтобы миллионы JAR-файлов, созданных до Java 9, продолжали работать. Ключ к безболезненной миграции — понять, каким видом становится конкретный JAR и что это полностью зависит от места его размещения. На этой странице описаны все три вида, показаны правила доступа между ними и приведена запускаемая программа, подтверждающая эти категории.

Именованные модули

Именованный (явный) модуль — это модуль с файлом module-info.class, помещённый на module path (--module-path / -p). Это полноправный модуль:

  • Он имеет имя, заданное в дескрипторе.
  • Он читает только те модули, которые указаны в requires.
  • Он раскрывает только пакеты, перечисленные в exports.

Это и есть строго инкапсулированный модуль, описанный в главе о декларации модуля. Все гарантии JPMS — объявленные зависимости, скрытые внутренности, раннее обнаружение ошибок — применяются именно к именованным модулям.

Автоматические модули

Автоматический модуль — это обычный JAR-файл (без module-info), помещённый на module path. JPMS оборачивает его в модуль, чтобы именованные модули могли указывать requires на него в процессе миграции — не дожидаясь, пока автор библиотеки добавит дескриптор. Автоматический модуль:

  • Получает имя, производное от имени JAR-файла (например, guava-32.1.jarguava), если только в манифесте JAR не задано Automatic-Module-Name.
  • Экспортирует все пакеты — у него нет директивы exports, поэтому все его пакеты открыты для всех.
  • Читает все остальные модули, включая безымянный, поэтому по-прежнему видит JAR-файлы из classpath.

Это мост: он позволяет начать писать именованные модули, зависящие от ещё не модуляризованных библиотек. Цена — полный отказ от инкапсуляции, а автоматически производное имя может измениться при переименовании JAR. Именно поэтому указание Automatic-Module-Name в манифесте — ответственное решение для библиотеки.

Безымянный модуль

Безымянный модуль — это «мусорное ведро» для classpath. Каждый класс, загружаемый из classpath, принадлежит безымянному модулю своего загрузчика классов. Он:

  • Не имеет имени (getName() возвращает null, isNamed() равно false).
  • Читает все остальные модули в системе.
  • Экспортирует все свои пакеты другим безымянным/автоматическим модулям.

Но существует намеренная односторонняя стена: именованный модуль не может указать requires на безымянный модуль. Вы не можете его назвать, значит, не можете от него зависеть. Именно это правило задаёт порядок миграции — именованный модуль может зависеть только от других именованных или автоматических модулей, но никогда от кода в classpath.

Матрица доступа

Кто может читать кого — это небольшая таблица:

От ↓ / К →NamedAutomaticUnnamed
Namedтолько если requiresтолько если requiresникогда
Automaticдадада
Unnamedдадада

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

Практический пример: определение вида модуля во время выполнения

Module API позволяет для любого класса узнать, является ли его модуль именованным и был ли он создан автоматически. Эта программа исследует три ссылки — собственный класс (classpath → безымянный), тип JDK (именованный) и загрузочный слой — чтобы сделать категории наглядными.

java— editable, runs on the server

Что следует из результатов выполнения:

  • Собственный класс программы классифицировался как UNNAMED с именем null, тогда как java.util.List и HttpClient классифицировались как NAMED (java.base, java.net.http). При запуске из classpath ваш код всегда безымянный; JDK всегда представляет собой набор именованных модулей. Вид модуля определяется способом его загрузки, а не чем-либо в самом классе.
  • java.base.canRead(self) вернул false, а self.canRead(java.base) вернул true. Это и есть односторонняя стена в действии: безымянный модуль читает всё, но ни один именованный модуль не читает безымянный. Эта асимметрия — именно причина того, почему именованный код не может указывать requires на код из classpath.
  • classify() различил автоматический и именованный модули по descriptor.isAutomatic(). Здесь вы не увидите true (ничего не было помещено на module path в виде обычного JAR), но эта проверка — именно то, как инструментарий сообщает об автоматическом модуле: реальный объект модуля с синтезированным, полностью открытым дескриптором.
  • isExported("java.util") вернул true, а isExported("jdk.internal.misc") вернул false, хотя оба являются реальными пакетами внутри java.base. Директива exports именованного модуля — это список разрешений; неэкспортированные (или только квалифицированно экспортированные) пакеты невидимы для внешнего кода, даже если являются public. Безымянный модуль, напротив, экспортирует всё, что содержит.
  • Для наблюдения за всем этим не потребовалось никакого module-info.java. Три категории — это факты времени выполнения о том, как был загружен класс; getModule() и getDescriptor() раскрывают их — те же самые вызовы, на которые опираются инструменты миграции, чтобы понять, с чем они работают.

Почему существуют три вида

Два вида совместимости — автоматический и безымянный — означают, что Java 9+ запускает немодифицированные приложения Java 8. Вы переходите на строгую инкапсуляцию по одному JAR за раз: оставьте всё в classpath (всё безымянное) — ничего не изменится; переместите библиотеку на module path без дескриптора — она станет автоматической; добавьте module-info.java — она станет именованной. Далее, службы модулей показывают механизм uses/provides, который развязывает модули, а миграция модулей проводит реальный проект через эти три состояния.

Практика

Практика
В процессе миграции вы помещаете сторонний JAR без 'module-info.class' с именем 'fastjson-2.0.jar' на MODULE PATH, затем пишете 'requires fastjson;' в своём именованном модуле. Какое утверждение правильно описывает 'fastjson' в данном случае?
В процессе миграции вы помещаете сторонний JAR без 'module-info.class' с именем 'fastjson-2.0.jar' на MODULE PATH, затем пишете 'requires fastjson;' в своём именованном модуле. Какое утверждение правильно описывает 'fastjson' в данном случае?
Was this page helpful?