1. Что такое module-info.java и где он находится?
Модуль в Java всегда начинается с файла module-info.java. Это своего рода «паспорт» модуля, где вы объявляете его имя, экспортируемые пакеты и зависимости от других модулей.
Где искать?
Файл module-info.java должен находиться в корне папки исходников вашего модуля. Например, если у вас структура проекта такая:
project-root/
└── src/
└── my.module.name/
├── module-info.java
└── com/
└── example/
└── api/
└── MyClass.java
Здесь my.module.name — это имя модуля (о правилах именования чуть позже).
Без этого файла модуль просто не считается модулем!
Если его нет — это обычный «старый» Java‑проект, пусть даже с кучей папок и классов.
2. Синтаксис module-info.java: основные элементы
Давайте сразу посмотрим на пример самого простого файла — это не страшно, честно!
module my.module.name {
exports com.example.api;
requires java.sql;
}
Разберём по кусочкам:
module my.module.name { ... }
Это объявление модуля с именем my.module.name. Имя модуля — это уникальный идентификатор, обычно совпадающий с корневым пакетом (например, com.example.app).
Забавный факт: если вы назовёте модуль java.base, то компилятор будет не в восторге. Не стоит пытаться подменять стандартные модули.
exports com.example.api;
Эта строчка говорит: «Я, модуль, готов поделиться всем, что находится в пакете com.example.api». Всё, что внутри этого пакета и помечено как public, будет видно другим модулям. Всё остальное — строго для своих.
requires java.sql;
А вот тут мы честно признаёмся компилятору: «Мне для работы нужен стандартный модуль java.sql». Без этого компилятор не даст нам использовать классы из этого модуля.
Дополнительные ключевые слова (для общего развития)
- opens <package>; — открывает пакет для рефлексии (например, для библиотек сериализации типа Jackson).
- uses <service-interface>; — говорит, что модуль использует некий сервис (интерфейс).
- provides <service-interface> with <implementation-class>; — сообщает, что модуль предоставляет реализацию сервиса.
В этой лекции мы сосредоточимся на exports и requires — они нужны в подавляющем большинстве учебных и боевых проектов.
3. Примеры module-info.java
Пример 1. Минимальный модуль
module com.example.hello {
exports com.example.hello.api;
}
- Этот модуль экспортирует только пакет com.example.hello.api.
- Всё, что лежит, например, в com.example.hello.internal, будет скрыто от других модулей, даже если там есть public-классы.
Пример 2. Модуль с зависимостью
module com.example.dbclient {
exports com.example.db.api;
requires java.sql;
}
Мы можем использовать JDBC, но только потому, что честно объявили зависимость от java.sql.
Пример 3. Несколько экспортируемых пакетов
module com.example.library {
exports com.example.library.api;
exports com.example.library.utils;
}
Можно экспортировать сколько угодно пакетов (но не стоит экспортировать всё подряд — в этом смысл модулей!).
4. Ограничения и правила
Имя модуля
- Обычно совпадает с корневым пакетом (например, com.example.app).
- Не должно совпадать с именами стандартных модулей (java.base, java.sql и т.п.).
- Не должно содержать пробелов, спецсимволов, начинаться с цифры и т.д.
- Рекомендация: используйте обратное доменное имя вашей организации или проекта, чтобы избежать конфликтов.
Один модуль — один module-info.java
В одном модуле может быть только один такой файл. Если их два — компилятор устроит вам «модульный скандал».
Экспорт пакета только из одного модуля
Один и тот же пакет не может экспортироваться из двух разных модулей. Это как если бы у вас было два паспорта на одно имя — государство не одобрит.
Пакеты внутри модуля
Можно экспортировать только те пакеты, которые реально существуют в структуре исходников этого модуля. Экспорт несуществующего пакета приведёт к ошибке компиляции.
5. Практика: создаём module-info.java в проекте
Допустим, у нас есть простой проект со следующим содержимым:
project-root/
└── src/
└── com.example.greetings/
├── module-info.java
└── com/
└── example/
└── greetings/
├── api/
│ └── Greeter.java
└── internal/
└── SecretSauce.java
Шаг 1. Создаём module-info.java
module com.example.greetings {
exports com.example.greetings.api;
}
Шаг 2. Пробуем использовать класс из internal‑пакета в другом модуле
Допустим, у нас есть второй модуль com.example.app, который хочет получить доступ к SecretSauce:
module com.example.app {
requires com.example.greetings;
}
import com.example.greetings.internal.SecretSauce; // ОШИБКА!
Результат:
Компилятор скажет: «Пакет com.example.greetings.internal не экспортируется модулем com.example.greetings». Даже если класс SecretSauce — public, он недоступен другим модулям.
Это и есть настоящая инкапсуляция на уровне модулей!
Шаг 3. Пробуем не объявить requires
Если в com.example.app мы не напишем requires com.example.greetings;, а попытаемся использовать класс из com.example.greetings.api, компилятор выдаст ошибку:
package com.example.greetings.api is not visible
6. Типичные ошибки при работе с module-info.java
Ошибка №1: Несовпадение имени модуля и структуры проекта.
Если вы назовёте модуль com.example.app, а структура папок будет src/main/java/app, компилятор не поймёт, что вы от него хотите. Имя модуля обычно совпадает с корневым пакетом, а папки должны это отражать.
Ошибка №2: Экспорт всего подряд.
Экспортировать нужно только то, что реально должно быть видно другим модулям. Не надо делать exports com.example; просто потому, что «так проще». Это нарушает инкапсуляцию.
Ошибка №3: Забыл добавить requires.
Если вы используете классы из другого модуля или стандартной библиотеки (например, java.sql), но забыли объявить зависимость — будет ошибка компиляции.
Ошибка №4: Несуществующий пакет в exports.
Если вы написали exports com.example.foo;, а такого пакета нет — компилятор скажет, что вы «экспортируете воздух».
Ошибка №5: Публичные классы в неэкспортируемом пакете.
Если класс объявлен public, но лежит в пакете, который не экспортируется, этот класс будет виден только внутри модуля. Это не ошибка, но часто становится неожиданностью для новичков.
Ошибка №6: Несколько module-info.java в одном модуле.
В одном модуле должен быть только один файл module-info.java. Если их два — компилятор не сможет собрать проект.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ