Жизненный цикл сборки Maven: фазы, цели плагинов и команды
Команда mvn clean jar:jar завершается BUILD SUCCESS, а в каталоге target появляется shop-1.0-SNAPSHOT.jar. Только классов в нём нет, а в логе стоит предупреждение JAR will be empty - no content was marked for inclusion!. Причина в том, что jar:jar — это цель плагина, а не фаза: Maven выполнил ровно её и не стал компилировать код. Команда mvn clean package дала бы рабочий jar, потому что фаза package тянет за собой все предыдущие этапы сборки.
Жизненный цикл сборки Maven (build lifecycle) — это заранее определённая упорядоченная последовательность фаз, через которые проходит проект: проверка, компиляция, тесты, упаковка, установка и публикация. В Maven три встроенных жизненных цикла: default (сборка), clean (очистка) и site (сайт проекта). Когда вы запускаете фазу, например mvn package, Maven выполняет все фазы этого цикла до неё включительно, а реальную работу на каждой фазе делают привязанные к ней цели (goals) плагинов.
Три жизненных цикла: default, clean, site
В Maven встроено три независимых жизненных цикла: default собирает проект от проверки POM до публикации артефакта, clean удаляет результаты прошлой сборки, site генерирует сайт проекта с отчётами. Фаза одного цикла не запускает фазы другого: mvn install не очищает target, а mvn clean ничего не компилирует.
| Жизненный цикл | Фазы (по порядку) | Что делает | Типичная команда |
|---|---|---|---|
default | 23 фазы от validate до deploy (см. ниже) | Компиляция, тесты, упаковка, установка в локальный репозиторий, публикация | mvn package, mvn verify, mvn install |
clean | pre-clean, clean, post-clean | Удаляет каталог сборки (target) целью clean:clean | mvn clean |
site | pre-site, site, post-site, site-deploy | Генерирует HTML-сайт проекта с отчётами (Javadoc, тесты, анализ кода) в target/site; site-deploy публикует его на сервер | mvn site |
Слово clean обозначает и сам жизненный цикл, и его главную фазу, отсюда путаница в старых статьях, где clean и site называют «фазами, стоящими особняком». Точнее так: это отдельные циклы, которые выполняются, только если их фаза явно указана в команде. Для mvn site используется maven-site-plugin, а какие отчёты попадут на сайт, задаёт раздел <reporting> в pom.xml.
В одной команде можно перечислить фазы разных циклов — Maven выполнит их слева направо:
mvn clean package site Здесь сначала отработает цикл clean до фазы clean, затем default до package, затем site до site: проект собирается «с нуля» и получает сайт с документацией.
Фазы жизненного цикла default
Жизненный цикл default состоит из 23 фаз, но в командах обычно используют семь основных: validate, compile, test, package, verify, install и deploy. Остальные фазы — промежуточные точки, к которым плагины привязывают подготовительные шаги: генерацию кода, копирование ресурсов, запуск окружения для интеграционных тестов.
| Фаза | Что происходит | Результат |
|---|---|---|
validate | Проверка, что POM корректен и вся нужная информация есть | Ошибка, если в pom.xml нет обязательных элементов |
compile | Компиляция основного кода (перед ней копируются ресурсы) | .class-файлы в target/classes |
test | Компиляция и запуск unit-тестов плагином maven-surefire-plugin | Отчёты в target/surefire-reports; упавший тест останавливает сборку |
package | Упаковка скомпилированного кода в формат из <packaging> | target/shop-1.0-SNAPSHOT.jar (или .war) |
verify | Проверки качества и результатов интеграционных тестов (если к фазе привязаны плагины) | Сборка падает, если проверка не прошла |
install | Копирование артефакта и POM в локальный репозиторий ~/.m2/repository | Другие проекты на этой машине могут подключить его как зависимость |
deploy | Публикация артефакта в удалённый репозиторий из <distributionManagement> | Артефакт доступен команде и CI-серверам |
Полный порядок фаз default (Maven 3), основные выделены в таблице выше:
validate -> initialize
-> generate-sources -> process-sources -> generate-resources -> process-resources
-> compile -> process-classes
-> generate-test-sources -> process-test-sources -> generate-test-resources -> process-test-resources
-> test-compile -> process-test-classes -> test
-> prepare-package -> package
-> pre-integration-test -> integration-test -> post-integration-test
-> verify -> install -> deploy Фазы с префиксами pre-, post- и process- напрямую почти никогда не вызывают: они нужны, чтобы плагин мог встроиться в правильное место сборки. Фаза, к которой не привязано ни одной цели, ничего не делает — например, для простого jar-проекта mvn validate только прочитает и проверит POM и выведет BUILD SUCCESS. Как install раскладывает файлы по каталогам локального репозитория, разобрано в уроке Виды репозиториев в Maven.
Запуск фазы выполняет все предыдущие
Главное правило жизненного цикла: команда mvn <phase> выполняет все фазы того же цикла от первой до указанной включительно. Поэтому mvn package сам скопирует ресурсы, скомпилирует код и прогонит тесты, а фазы после package (verify, install, deploy) не запустятся.
mvn package В выводе Maven каждая выполненная цель отмечена строкой вида --- plugin:version:goal (execution-id) @ project ---, и по ним видно, что произошло на самом деле. Вывод (фрагмент, Maven 3.9; версии плагинов зависят от версии Maven):
[INFO] --- resources:3.3.1:resources (default-resources) @ shop ---
[INFO] --- compiler:3.13.0:compile (default-compile) @ shop ---
[INFO] --- resources:3.3.1:testResources (default-testResources) @ shop ---
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ shop ---
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] Building jar: /home/user/shop/target/shop-1.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS Фазы без привязанных целей (validate, initialize, generate-sources, prepare-package и другие) в логе не упоминаются: для jar-проекта на них ничего не выполняется. Префикс default- в скобках означает, что цель пришла из стандартной привязки, а не из вашего pom.xml. Если тест падает, Maven останавливается на фазе test, и jar не создаётся.
Команда mvn deploy пройдёт все фазы default, но в учебном проекте завершится ошибкой на последней: не указано, куда публиковать.
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.1.2:deploy (default-deploy) on project shop:
Deployment failed: repository element was not specified in the POM inside distributionManagement element
or in -DaltDeploymentRepository=id::url parameter Фаза и цель (goal): в чём разница
Фаза — это шаг жизненного цикла, сама по себе она ничего не делает. Цель (goal) — конкретное действие плагина, например compiler:compile или jar:jar. При запуске фазы Maven выполняет все предыдущие фазы того же цикла и привязанные к ним цели. При запуске цели выполняется только она одна, без предыдущих шагов.
| Признак | Фаза | Цель (goal) |
|---|---|---|
| Пример | package | jar:jar |
| Кто выполняет работу | Цели, привязанные к фазе | Плагин (maven-jar-plugin) |
| Что выполняется при запуске | Все фазы цикла до неё включительно | Только эта цель |
| Синтаксис | mvn package | mvn jar:jar или mvn org.apache.maven.plugins:maven-jar-plugin:3.4.1:jar |
Короткая форма jar:jar — это префикс плагина и имя цели. Цели вызывают напрямую для разовых действий, которые не входят в сборку: mvn dependency:tree, mvn help:effective-pom, mvn versions:display-dependency-updates. Фазы и цели можно смешивать в одной команде: mvn clean compile dependency:tree выполнит цикл clean, затем default до compile и только потом цель dependency:tree.
Отсюда ошибка из начала урока: mvn clean jar:jar удалил target и сразу упаковал несуществующий каталог классов. Вывод:
[INFO] --- clean:3.2.0:clean (default-clean) @ shop ---
[INFO] Deleting /home/user/shop/target
[INFO] --- jar:3.4.1:jar (default-cli) @ shop ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: /home/user/shop/target/shop-1.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS Id выполнения default-cli показывает, что цель вызвана из командной строки, а не фазой. Чтобы собрать jar, запускайте фазу: mvn clean package.
Привязка целей плагинов к фазам
Какие цели выполняются на каждой фазе, определяет элемент <packaging> в pom.xml: для каждого типа упаковки в Maven встроен свой набор стандартных привязок (lifecycle mapping). Поэтому jar-проект собирается без единого объявленного плагина, а war и pom на тех же фазах делают другое. Свои цели можно привязать к любой фазе в разделе <build><plugins>.
Стандартные привязки для packaging = jar (Maven 3.9.x):
| Фаза | Цель плагина | Что делает |
|---|---|---|
process-resources | resources:resources | Копирует src/main/resources в target/classes |
compile | compiler:compile | Компилирует src/main/java |
process-test-resources | resources:testResources | Копирует src/test/resources в target/test-classes |
test-compile | compiler:testCompile | Компилирует src/test/java |
test | surefire:test | Запускает unit-тесты |
package | jar:jar | Создаёт jar в target |
install | install:install | Копирует jar и POM в ~/.m2/repository |
deploy | deploy:deploy | Публикует в удалённый репозиторий |
У war на фазе package вместо jar:jar выполняется war:war. У pom (родительские и агрегирующие POM) остаются только install и deploy: компиляции и тестов нет. Как значение packaging задаётся и на что ещё влияет, описано в разделе Элемент packaging, а как привязать к фазе свою цель через <executions> — в уроке Использование плагинов в Maven. Если к одной фазе привязано несколько целей, сначала выполняются цели из стандартной привязки, затем объявленные в pom.xml — в порядке объявления.
Таблица выше справедлива для «чистого» проекта. Проверить фактические привязки вашего проекта (с учётом родителя и версий плагинов) можно командой mvn help:describe:
mvn help:describe -Dcmd=compile Вывод (фрагмент):
[INFO] 'compile' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile
It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
...
* process-resources: org.apache.maven.plugins:maven-resources-plugin:3.3.1:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile
...
* test: org.apache.maven.plugins:maven-surefire-plugin:3.5.2:test
* package: org.apache.maven.plugins:maven-jar-plugin:3.4.1:jar Not defined означает, что к фазе ничего не привязано. Описание целей конкретного плагина выводит mvn help:describe -Dplugin=surefire -Ddetail. Официальный список фаз и привязок — в документации Introduction to the Build Lifecycle.
Unit- и интеграционные тесты: surefire и failsafe
Unit-тесты запускает maven-surefire-plugin на фазе test, он подключён по умолчанию. Интеграционные тесты запускает maven-failsafe-plugin: его нужно объявить в pom.xml. Он работает на фазах integration-test и verify, поэтому интеграционные тесты запускают командой mvn verify, а не mvn integration-test.
| Признак | Surefire (unit-тесты) | Failsafe (интеграционные тесты) |
|---|---|---|
| Подключение | По умолчанию, привязан к фазе test | Только явно в <build><plugins> |
| Фазы | test | integration-test (запуск) и verify (проверка результата) |
| Имена классов по умолчанию | Test*, *Test, *Tests, *TestCase | IT*, *IT, *ITCase |
| Если тест упал | Сборка сразу останавливается на test | Сборка доходит до post-integration-test (окружение успевает остановиться) и падает на verify |
| Отчёты | target/surefire-reports | target/failsafe-reports |
| Пропустить | -DskipTests | -DskipITs (только интеграционные) или -DskipTests (все) |
Интеграционные тесты выполняются после package, так как им часто нужен собранный артефакт и внешнее окружение: база данных, контейнер, поднятый сервер. Запуск окружения привязывают к pre-integration-test, остановку — к post-integration-test. Минимальное подключение failsafe:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> Элемент <phase> здесь не нужен: у целей failsafe есть фазы по умолчанию, совпадающие с их именами. Вывод mvn verify для проекта с классом OrderRepositoryIT (фрагмент):
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] --- failsafe:3.5.2:integration-test (default) @ shop ---
[INFO] Running com.examclouds.shop.OrderRepositoryIT
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- failsafe:3.5.2:verify (default) @ shop ---
[INFO] BUILD SUCCESS Подробности настройки — в документации Maven Failsafe Plugin. Где хранить тестовые классы и ресурсы, показано в разделе Структура Maven-проекта.
Почему не mvn integration-test
Команда mvn integration-test останавливается на одноимённой фазе: post-integration-test не выполняется, и поднятая для тестов база или сервер остаются запущенными. Цель failsafe:verify на фазе verify тоже не выполняется, поэтому упавшие интеграционные тесты не роняют сборку — можно получить BUILD SUCCESS с красными тестами в отчёте. Запускайте mvn verify (или более поздние фазы: install, deploy).
Основные команды сборки
В повседневной работе хватает нескольких команд: mvn clean verify — полная проверка проекта, mvn clean install — то же плюс установка в локальный репозиторий, -DskipTests — сборка без запуска тестов, -pl — сборка отдельных модулей многомодульного проекта.
| Команда | Что делает | Когда использовать |
|---|---|---|
mvn clean verify | Очистка, компиляция, все тесты и проверки; артефакт остаётся в target | Проверка перед коммитом, сборка на CI |
mvn clean install | То же плюс копирование артефакта в ~/.m2/repository | Когда собранный модуль нужен другому проекту на этой машине |
mvn package -DskipTests | Тесты компилируются, но не запускаются; в логе Tests are skipped. | Быстро получить jar, когда тесты уже проверены |
mvn package -Dmaven.test.skip=true | Тесты не компилируются и не запускаются | Редко: если тестовый код временно не компилируется |
mvn -pl shop-core -am install | Собирает модуль shop-core и модули, от которых он зависит (-am, also make) | Многомодульный проект, нужен один модуль |
mvn -pl shop-core -amd verify | Собирает модуль и модули, которые зависят от него (-amd) | Проверить, что изменение не сломало зависимые модули |
mvn verify -rf :shop-web | Продолжает упавшую сборку с модуля shop-web (resume from) | После исправления ошибки в середине многомодульной сборки |
mvn -o package / mvn -U package | Офлайн-режим / принудительная проверка обновлений SNAPSHOT-зависимостей | Нет сети / нужна свежая SNAPSHOT-версия из удалённого репозитория |
mvn package -DskipTests Вывод (фрагмент):
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests are skipped.
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] BUILD SUCCESS Флаг -DskipTests предпочтительнее -Dmaven.test.skip=true: тестовый код хотя бы компилируется, и ошибка в нём не останется незамеченной. Кроме того, если другой модуль подключает ваши тестовые классы как test-jar, с maven.test.skip этот jar не будет создан, и сборка зависимого модуля упадёт на разрешении зависимости. Ключ -pl принимает путь к каталогу модуля или :artifactId, несколько модулей перечисляются через запятую.
mvn clean install по привычке
mvn clean install пишут почти рефлекторно, но для проверки проекта достаточно mvn clean verify: выполняются те же тесты и проверки, а локальный репозиторий не засоряется промежуточными сборками. В многомодульном проекте Maven сам передаёт модулям результаты соседних модулей внутри одной сборки (reactor), устанавливать их в ~/.m2 для этого не нужно. install нужен, когда артефакт должен подхватить другой, отдельно собираемый проект.
Что меняется в Maven 4
В Maven 4 (на ноябрь 2026 года в стадии preview, рекомендуемой остаётся ветка 3.x) жизненный цикл совместим с Maven 3: те же фазы и команды mvn clean verify и mvn install. Главное изменение — вместо фаз pre-* и post-* появились хуки before: и after: для любой фазы (например, before:integration-test), а старые имена сохранены для совместимости.
Для учебных проектов на Maven 3.9.x и 3.10.x всё, что описано в уроке, работает без изменений. Какая версия Maven установлена, показывает mvn -version, а зафиксировать её для всей команды помогает Maven Wrapper (см. урок Что такое Maven).
На чём чаще всего спотыкаются
Ошибки с жизненным циклом редко выглядят как ошибки: сборка проходит, но выполнено не то, что ожидалось. В таблице — что запустили, что получили и как исправить.
| Что сделали | Что произойдёт | Как исправить |
|---|---|---|
mvn clean jar:jar вместо mvn clean package | BUILD SUCCESS, но jar без классов; JAR will be empty в логе | Запускать фазу, а не цель: mvn clean package |
Создали OrderRepositoryIT, но не подключили failsafe | Тест не запускается нигде: surefire не берёт *IT, а failsafe нет | Объявить maven-failsafe-plugin с целями integration-test и verify |
mvn integration-test | Окружение тестов не останавливается, упавшие IT не роняют сборку | mvn verify |
mvn surefire:test после правки кода | Тесты идут по старым .class-файлам: компиляции не было | mvn test |
mvn deploy в проекте без <distributionManagement> | Deployment failed: repository element was not specified in the POM inside distributionManagement element | Для локальной проверки достаточно verify или install; для публикации указать репозиторий |
-Dmaven.test.skip=true в проекте с test-jar | Зависимый модуль не находит тестовый jar | Использовать -DskipTests |
Удалили или переименовали класс и собрали без clean | Старый .class может остаться в target/classes и попасть в jar | mvn clean package |
Часто задаваемые вопросы
Нужно ли запускать mvn clean перед каждой сборкой?
Нет. Компилятор сам пересобирает изменённые классы, и без clean сборка быстрее. clean нужен, когда классы удалялись или переименовывались (их старые .class могут остаться в target), после смены версии Java или плагинов, а также на CI, где сборка всегда должна идти с нуля.
Как запустить только один тестовый класс или метод?
Параметром surefire -Dtest: mvn test -Dtest=OrderServiceTest или mvn test -Dtest=OrderServiceTest#create, допускаются шаблоны (-Dtest=Order*Test). Для интеграционных тестов то же делает -Dit.test=OrderRepositoryIT с командой mvn verify. В многомодульном проекте добавьте -Dsurefire.failIfNoSpecifiedTests=false, иначе модули без такого теста завершатся ошибкой.
Можно ли добавить в жизненный цикл свою фазу?
Обычно в этом нет необходимости: свою цель привязывают к одной из существующих фаз, например запуск генератора кода — к generate-sources, проверку стиля — к verify. Собственный жизненный цикл или тип packaging можно описать только в расширении Maven (extension), это задача авторов плагинов. Как привязать цель к фазе, показано в уроке Использование плагинов в Maven.
Почему mvn install в одном проекте не обновляет зависимость в другом?
Чаще всего версии не совпадают: второй проект ссылается в <dependency> на другую версию, чем та, что указана в pom.xml первого. Если версии совпадают, IDE могла не переимпортировать проект — обновите Maven-проект в IDE. Для SNAPSHOT из удалённого репозитория используйте mvn -U. Куда именно install кладёт файлы, разобрано в уроке Виды репозиториев в Maven.
Практика по теме
Закрепите урок на задачах — у каждой есть уровень и оценка времени.
- Фазы и цели: что на самом деле выполняет mvn package
- Исполняемый jar, Javadoc и проверка кода плагинами Maven
Комментарии