W3docs

Объявление модуля Java: module-info.java

Объявление модуля Java в module-info.java: директивы requires, exports, opens, uses, provides — с примерами.

Модуль объявляется в специальном файле module-info.java, расположенном в корне дерева исходников модуля (рядом с верхним пакетом, но не внутри него). Этот файл компилируется в module-info.class. В нём нет обычного кода — только блок module со списком директив, описывающих границу модуля.

Структура файла

module com.acme.orders {
    requires com.acme.common;       // I depend on this module
    requires transitive java.sql;   // ...and so does anyone who requires me

    exports com.acme.orders.api;            // public to everyone
    exports com.acme.orders.spi to com.acme.web;  // public to one module only

    opens com.acme.orders.model;    // deep reflective access (e.g. for Jackson)

    uses com.acme.orders.PricingRule;            // I consume this service
    provides com.acme.orders.PricingRule          // I supply an implementation
        with com.acme.orders.StandardPricing;
}

Имя модуля (com.acme.orders) — это точечный идентификатор, который по соглашению является обратным DNS-префиксом содержащихся в нём пакетов. Это не пакет — у него собственное пространство имён, и два модуля не могут совместно использовать один пакет.

requires — объявление зависимостей

requires <module> означает «мне нужны экспортируемые пакеты этого модуля для компиляции и выполнения». Если требуемый модуль отсутствует, резолвер завершит работу с ошибкой при запуске. Два важных модификатора:

  • requires transitiveреэкспортирует зависимость. Любой модуль, зависящий от вас, автоматически читает её тоже. Используйте его, когда типы требуемого модуля присутствуют в ваших собственных публичных сигнатурах (метод, возвращающий java.sql.Connection, вынуждает вызывающих видеть java.sql).
  • requires static — зависимость только на этапе компиляции, необязательная во время выполнения (для процессоров аннотаций, необязательных интеграций).

java.base подключается неявно — его никогда не нужно писать явно.

exports — объявление публичного API

exports <package> делает public-типы этого пакета видимыми для других модулей. Всё, что не экспортировано, надёжно инкапсулировано — невидимо, даже если помечено как public. Квалифицированная форма, exports <package> to <module>, <module>, сужает видимость до именованного списка разрешений, что удобно для пакетов SPI, которые разделяются только между вашими собственными модулями.

Обратите внимание: exports работает на уровне пакета и не является рекурсивным — экспорт com.acme.api не экспортирует com.acme.api.internal.

opens — разрешение глубокого рефлексивного доступа

exports предоставляет доступ на этапе компиляции к public-членам, но не предоставляет рефлексивного доступа к непубличным членам. Такие фреймворки, как Jackson, Hibernate и Spring, используют setAccessible(true) для доступа к private-полям — для этого нужна директива opens:

  • opens <package> — предоставляет рефлексивный доступ во время выполнения (включая private-члены) всем модулям.
  • opens <package> to <module> — квалифицированная форма, только для указанных модулей.
  • open module com.acme.orders { … } — открывает каждый пакет (грубый инструмент для миграции).

Различие важно: пакет API вы exports-уете, но пакет классов данных, над которым должен работать сериализатор, вы opens-аете, не делая его частью API времени компиляции.

uses / provides — сервисы

Эти директивы реализуют паттерн ServiceLoader: uses <Service> объявляет, что вы потребляете интерфейс сервиса, а provides <Service> with <Impl> объявляет реализацию. У них есть отдельная глава — см. Сервисы модулей — здесь лишь отметим, что они находятся в том же дескрипторе.

Практический пример: построение дескриптора через API

Обычно вы пишете module-info.java и позволяете javac создать дескриптор. Но та же структура доступна программно через ModuleDescriptor.newModule(...), что является точным отражением синтаксиса директив — построение такого дескриптора — наглядный способ увидеть, во что превращается каждая директива.

java— editable, runs on the server

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

  • Названия методов конструктора совпадают один к одному с директивами: .requires(...), .exports(...), .opens(...), .uses(...), .provides(...). Читая вывод, можно убедиться, что дескриптор содержит ровно ту информацию, что и module-info.java — доказательство того, что файл представляет собой чистые метаданные, а не исполняемый код.
  • Зависимость java.sql выведена с модификатором [TRANSITIVE], а com.acme.common — без него. Именно этот модификатор реэкспортирует java.sql в нижестоящие модули; обычная зависимость оставляет её приватной для данного модуля.
  • Два экспорта выведены по-разному: com.acme.orders.api как "(to all)", а com.acme.orders.spi как "to [com.acme.web]". Квалифицированный экспорт хранит список целевых модулей внутри дескриптора — резолвер применяет его принудительно, поэтому ни один другой модуль не может прочитать SPI-пакет.
  • opens появился в отдельной секции, независимо от exports. Дескриптор хранит доступ во время компиляции и рефлексивный доступ во время выполнения как различные факты — именно поэтому сериализатору нужна директива opens, даже если пакет уже экспортирован.
  • uses и provides записаны вместе с остальными директивами — объявления сервисов являются частью границы модуля, а не отдельным конфигурационным файлом, как это было на classpath (META-INF/services). В главе Сервисы модулей эти директивы превращаются в работающий ServiceLoader.

Распространённые ошибки

  • Размещение module-info.java внутри директории пакета — он должен находиться в корне исходников.
  • Путаница между exports (доступ во время компиляции, публичные члены) и opens (доступ во время выполнения, все члены). JsonMappingException о недоступном поле почти всегда означает отсутствующую директиву opens.
  • Забытый requires transitive, когда публичные методы раскрывают типы другого модуля, вынуждая каждого вызывающего добавлять зависимость вручную.

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

Практика

Практика
Модуль 'com.acme.orders' имеет публичный метод, возвращающий 'java.sql.Connection'. Вызывающие в других модулях не могут скомпилировать код, потому что не видят тип 'java.sql.Connection', хотя уже используют 'requires com.acme.orders'. Какая директива в 'com.acme.orders' решит эту проблему, не заставляя каждого вызывающего добавлять 'requires java.sql' самостоятельно?
Модуль 'com.acme.orders' имеет публичный метод, возвращающий 'java.sql.Connection'. Вызывающие в других модулях не могут скомпилировать код, потому что не видят тип 'java.sql.Connection', хотя уже используют 'requires com.acme.orders'. Какая директива в 'com.acme.orders' решит эту проблему, не заставляя каждого вызывающего добавлять 'requires java.sql' самостоятельно?
Was this page helpful?