Многомодульный проект Maven: наследование, агрегация и reactor - Вопросы

Всего: 5 вопросов

1. 

Что такое многомодульный проект Maven и чем наследование (parent) отличается от агрегации (modules)?

Многомодульный проект - проект из нескольких модулей, у каждого свой pom.xml и свой артефакт; собираются они одной командой из корневого POM. Устройство держится на двух независимых механизмах.

  • Наследование: модуль указывает родителя в <parent> (по GAV) и получает общие настройки - groupId, version, свойства, управляемые версии, плагины. Ссылка идёт от модуля к родителю; родитель о наследниках не знает.
  • Агрегация: корневой POM перечисляет каталоги модулей в <modules>, и reactor собирает их за один запуск в порядке зависимостей. Ссылка идёт от агрегатора к модулям; модуль может не знать, что его агрегируют.

И родителю, и агрегатору нужен <packaging>pom</packaging>. Без него действует jar, и сборка останавливается с ошибкой вида Invalid packaging for parent POM ..., must be "pom" but is "jar" или Aggregator projects require 'pom' as packaging.

Чаще всего обе роли выполняет один корневой POM (как shop-parent), но это не обязательно: корпоративный родитель может жить в репозитории, а у проекта будет свой агрегатор; модулю можно не указывать parent вовсе - тогда он сам задаёт groupId, version и версии плагинов. Важное следствие: <parent> в модуле не включает его в сборку - забытая строка в <modules> даёт BUILD SUCCESS без этого модуля.

2. 

Что модуль многомодульного проекта получает от родительского POM и как Maven находит родителя (relativePath)?

Наследуются: groupId и version, properties (одноимённое свойство модуля переопределяет родительское), dependencyManagement и pluginManagement (только версии и настройки), dependencies - они подключаются в каждый модуль, build/plugins с executions, repositories, distributionManagement и метаданные. Поэтому в <dependencies> родителя кладут только то, что нужно всем модулям, остальное - в dependencyManagement.

Не наследуются: artifactId, name и prerequisites (по документации Maven), а также packaging и modules: модуль без своего packaging получает jar, а список модулей действует только в своём POM.

Поиск родителя в Maven 3: сначала по <relativePath> (по умолчанию ../pom.xml) со сверкой groupId, artifactId и version; если файла нет или координаты не совпадают - в локальном и удалённых репозиториях. Родителя в другом месте указывают явно (../shop-parent/pom.xml), пустой <relativePath/> - «только из репозиториев» (внешние родители вроде spring-boot-starter-parent). Не нашли - ошибка Non-resolvable parent POM ещё при чтении модели; частая причина - неверный путь или версия в <parent> после ручной правки.

3. 

Что такое reactor в Maven, почему модуль может не видеть соседний модуль и как собрать только часть многомодульного проекта?

Reactor собирает модули из <modules> (рекурсивно), строит граф связей (зависимости, родительские POM, плагины и расширения из соседних модулей) и сортирует модули так, чтобы каждый собирался после тех, от которых зависит. Внутри одной сборки он передаёт модулю результаты соседей напрямую, без установки в ~/.m2.

Почему не видно соседа. Если сосед не участвует в сборке (запуск из подкаталога или -pl без -am), он - обычная зависимость из репозиториев: нет в ~/.m2 - ошибка Could not find artifact, есть старая установка - сборка проходит со старым кодом.

  • -pl - только перечисленные модули (каталог или :artifactId);
  • -am - плюс модули, от которых они зависят; -amd - плюс модули, которые зависят от них;
  • -rf :shop-web - продолжить упавшую сборку; пропущенные модули берутся из ~/.m2;
  • -pl '!shop-web' - исключить модуль (с Maven 3.2.1, в bash в кавычках); модуль, от которого зависят другие участники, исключают, только если его артефакт уже есть в ~/.m2;
  • -T 4 или -T 1C - параллельная сборка независимых модулей; плагины без пометки thread-safe Maven отмечает предупреждением, при странных падениях соберите последовательно.

Из каталога модуля можно указать корень явно: mvn -f ../pom.xml -pl shop-web -am package. Урок описывает Maven 3.9.x; в Maven 4 работа из подкаталогов меняется.

4. 

Как управлять версией многомодульного проекта Maven: ${project.version}, versions:set и CI-friendly версия ${revision}?

Обычно у всех модулей одна версия: она задана в родителе, модули её наследуют.

Версии соседей в <dependency> пишут как ${project.version} или задают один раз в dependencyManagement родителя. Литерал 1.0-SNAPSHOT ломается при повышении версии: модули станут 1.1-SNAPSHOT, а зависимость останется старой - reactor её не узнает и возьмёт старый артефакт из ~/.m2 или упадёт.

Версия в <parent> в Maven 3 должна быть литералом, поэтому меняется во всех POM. Способы:

  • mvn versions:set -DnewVersion=1.1.0-SNAPSHOT (versions-maven-plugin, MojoHaus) меняет версию родителя и ссылки в модулях reactor, оставляя pom.xml.versionsBackup; затем versions:commit или versions:revert. Литеральные версии соседей в зависимостях плагин может и не обновить.
  • CI-friendly версии (с Maven 3.5.0): ${revision}, ${sha1}, ${changelist} - единственное исключение из правила литералов. Версия задаётся свойством, CI передаёт свою: mvn clean deploy -Drevision=1.0.0; значение по умолчанию можно хранить в .mvn/maven.config.

Подвох ${revision}: без настроек в ~/.m2 и удалённый репозиторий попадают POM с буквальным ${revision}, и внешний проект не разрешит родителя. Поэтому подключают flatten-maven-plugin: в режиме resolveCiFriendliesOnly он пишет .flattened-pom.xml с подставленной версией, и устанавливается он (файлы добавляют в .gitignore).

5. 

Зачем делить проект на модули Maven, когда это не нужно и куда класть код, общий для нескольких модулей?

Зачем: модули разделяют код по слоям или функциям с явными границами - модуль видит только те модули, которые объявлены у него в зависимостях. Каждый модуль собирается в собственный артефакт (jar, war), его можно тестировать и переиспользовать отдельно, а общие версии и настройки хранятся в одном родительском POM.

Пример: shop-model (классы предметной области), shop-service (бизнес-логика, зависит от shop-model), shop-web (веб-слой, зависит от shop-service). Если зависимость на модуль доступа к данным не объявлена, веб-слой не обратится к базе в обход сервисов: компилятор отклонит такой код. У корневого проекта кода нет: его pom.xml только связывает модули и хранит общие настройки.

Когда не нужно: модули оправданы, если у частей проекта разные зависимости, разные потребители или нужна независимая сборка. Учебный проект на десяток классов делить не стоит: модули добавляют POM-файлы и время сборки, но не дают выигрыша.

Общий код выносят в отдельный модуль (например, shop-common или shop-api), от которого зависят потребители. В родительский POM его не положить: у него packaging pom, исходники в нём не компилируются. Общий модуль заодно разрывает циклические зависимости, которые reactor не допускает.

Модуль Maven и модуль Java (JPMS, с Java 9) - разные механизмы: первый - единица сборки (свой pom.xml и артефакт), второй - единица инкапсуляции, где module-info.java перечисляет экспортируемые пакеты и нужные модули. Их можно сочетать, но ни один не требует другого.

Страница 1 из 1