Инструмент сборки: Maven/Gradle
Познакомимся с профессиональными инструментами сборки Java-проектов. Разберём Maven и Gradle — два самых популярных сборщика, которые автоматизируют управление зависимостями, компиляцию, тестирование и упаковку приложений
Зачем нужны инструменты сборки?
Представьте, что вы только что начали изучать Java. Вы создаёте файл Main.java и компилируете его одной командой javac Main.java. Затем запускаете java Main. Всё работает. Но что произойдёт, когда ваш проект вырастет?
Допустим, вы разрабатываете веб-приложение. вам понадобится библиотека для работы с базой данных (Hibernate), библиотека для HTTP-запросов (OkHttp), библиотека для логирования (Log4j), библиотека для работы с JSON (Jackson) и ещё десяток других. Каждая из этих библиотек состоит из JAR-файлов, каждый JAR-файл может зависеть от других JAR-файлов. В результате получается сложная сеть зависимостей, которую нужно отслеживать вручную.
Без инструмента сборки вам пришлось бы:
- Вручную скачивать каждый JAR-файл с официального сайта
- Вручную отслеживать, какие версии библиотек совместимы друг с другом
- Помнить, что нужно скомпилировать 50+ Java-файлов в правильном порядке
- Вручную управлять классификацией классов (classpath)
- Самостоятельно упаковывать приложение в JAR или WAR
- Каждый раз запускать длинную команду компиляции из десятков аргументов
Именно здесь на помощь приходят инструменты сборки — программы, которые автоматизируют весь этот процесс. Они берут на себя управление зависимостями, компиляцию, тестирование, упаковку и деплой. Вы просто описываете, что хотите получить, а инструмент сборки делает всё остальное.
Аналогия: Инструмент сборки — это как шеф-повар на кухне ресторана. Вы даёте ему рецепт (конфигурационный файл), а он сам закупает продукты (библиотеки), обрабатывает их (компилирует), готовит блюдо (тесты) и подаёт на стол (упаковывает в JAR). Вам не нужно бегать по магазинам и самостоятельно нарезать овощи.
Проблема ручного управления зависимостями
Давайте рассмотрим типичную проблему, с которой сталкивается каждый разработчик. Вы хотите использовать библиотеку Gson для работы с JSON. Скачиваете JAR-файл, кладёте в папку lib, добавляете в classpath. Всё работает.
Но вот выясняется, что Gson зависит от Guava, а Guava зависит от ещё трёх библиотек. Вы скачиваете их все. Затем обнаруживаете, что Gson версии 2.10 несовместим с Guava версии 31.0. Придётся искать совместимые версии.
Ещё хуже, когда в проекте используются две библиотеки, которые зависят от разных версий одной и той же библиотеки. Это называется конфликт зависимостей. Например, ваш проект использует Spring версии 5.3 и Hibernate версии 5.6, и обе они зависят от Jackson, но от разных версий. Какую версию выбрать?
Инструменты сборки решают эти проблемы автоматически:
- Автоматическая загрузка — инструмент сам скачивает нужные библиотеки из репозиториев
- Разрешение конфликтов — выбирает совместимые версии зависимостей
- Транзитивные зависимости — автоматически загружает зависимости зависимостей
- Кэширование — сохраняет скачанные библиотеки локально, не качая заново
- Изоляция — каждый проект может использовать свои версии библиотек
Почему javac и java недостаточно?
Компилятор javac и виртуальная машина java — это замечательные инструменты, но они решают только узкий круг задач. Компилятор знает, как превратить .java файлы в .class файлы, а JVM знает, как их выполнить. Но они не знают:
- Как скачать библиотеку из интернета
- Какую версию библиотеки использовать
- Как собрать все class-файлы в один JAR
- Как запустить тесты перед сборкой
- Как создать документацию к проекту
- Как автоматизировать процесс сборки в CI/CD
Попробуйте собрать大型 проект вручную с помощью только javac. Вам придётся написать скрипт, который компилирует все файлы в правильном порядке, передаёт все библиотеки через classpath, упаковывает результат в JAR и генерирует манифест. Это утомительно и подвержено ошибкам.
Apache Maven — философия «соглашение вместо конфигурации»
Maven появился в 2004 году как проект Apache Software Foundation. Его главная философия — «convention over configuration» (соглашение вместо конфигурации). Это означает, что Maven ожидает от вас определённую структуру проекта, и если вы следуете этой структуре, вам не нужно ничего настраивать.
Например, Maven ожидает, что исходный код Java будет лежать в папке src/main/java, ресурсы — в src/main/resources, тесты — в src/test/java, а ресурсы тестов — в src/test/resources. Если вы соблюдаете эту структуру, Maven автоматически знает, что нужно компилировать, где искать ресурсы, как запускать тесты.
Стандартная структура Maven-проекта:
my-project/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── App.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── AppTest.java
│ └── resources/
│ └── test-data.properties
Эта структура не случайна. Каждая папка имеет своё назначение. src/main/java — основной исходный код. src/main/resources — файлы, которые должны попасть в финальный артефакт (конфигурации, изображения, XML-схемы). src/test/java — код тестов, который не попадёт в продакшн-сборку.
Почему convention over configuration? Представьте, что вы заходите в кафе. В каждом кафе кофе подают в чашке, а не в тарелке. Вы не просите «пожалуйста, подайте кофе в чашке» — это уже подразумевается. Точно так же Maven подразумевает стандартную структуру. Это упрощает жизнь разработчиков, потому что любой Java-программист, открыв Maven-проект, сразу понимает, где что лежит.
pom.xml — сердце Maven-проекта
Файл pom.xml (Project Object Model) — это конфигурационный файл Maven. Он описывает проект: его имя, версию, зависимости, плагины, профили и многое другое. Это XML-файл, который Maven читает при каждом запуске.
Каждый Maven-проект однозначно идентифицируется тремя полями:
- groupId — обратный домен организации (например,
com.example). Обычно совпадает с корневым пакетом Java. - artifactId — имя проекта (например,
my-app). Это имя JAR-файла без расширения. - version — версия проекта (например,
1.0.0). Используется для отслеживания изменений.
Пример минимального pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-application</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Моё приложение</name>
<description>Пример Maven-проекта</description>
</project>
Поле packaging определяет тип собираемого артефакта. Возможные значения: jar (по умолчанию), war, ear, pom. Для большинства Java-приложений используется jar.
Уникальный идентификатор проекта формируется как groupId:artifactId:version. Например: com.example:my-application:1.0.0. Этот triple (GAV) используется Maven для поиска и скачивания библиотек.
Maven-репозитории — откуда берутся библиотеки?
Maven хранит все библиотеки в репозиториях — специальных серверах с JAR-файлами. Когда вы указываете зависимость в pom.xml, Maven автоматически скачивает её из репозитория.
Существует три типа репозиториев:
- Локальный репозиторий — папка на вашем компьютере (обычно
~/.m2/repository), где Maven хранит скачанные библиотеки. Если библиотека уже скачана, Maven берёт её оттуда, не обращаясь к сети. - Центральный репозиторий — официальный сервер Maven (repo.maven.apache.org), содержащий более 400 000 библиотек. Это основной источник для большинства зависимостей.
- Удалённый (корпоративный) репозиторий — сервер внутри вашей организации (например, Nexus или Artifactory), где хранятся внутренние библиотеки и кэшируются внешние.
Порядок поиска: Maven сначала ищет в локальном репозитории. Если не находит — обращается к удалённым репозиториям (сначала к корпоративным, затем к центральному). Скачанная библиотека сохраняется в локальный репозиторий для повторного использования.
Интересный факт: Центральный репозиторий Maven хранит более 400 000 артефактов от десятков тысяч разработчиков. Каждый день через него проходят миллионы скачиваний. Это делает Maven Central одним из крупнейших репозиториев программного обеспечения в мире.
Жизненный цикл Maven
Maven организует процесс сборки в последовательность этапов, называемых жизненным циклом (lifecycle). Каждый этап (phase) выполняет определённую группу задач. Вот основные фазы:
| Фаза | Описание |
|---|---|
clean |
Удаляет папку target с предыдущей сборкой |
validate |
Проверяет корректность структуры проекта |
compile |
Компилирует исходный код в target/classes |
test |
Запускает单元 тесты (требует предварительной компиляции) |
package |
Упаковывает классы в JAR/WAR/EAR |
verify |
Запускает интеграционные тесты и проверки |
install |
Устанавливает JAR в локальный репозиторий |
deploy |
Отправляет JAR в удалённый репозиторий |
Когда вы запускаете mvn package, Maven выполняет все предыдущие фазы последовательно: validate → compile → test → package. Это называется «привязка» — каждая фаза автоматически запускает все предыдущие.
Самые частые команды Maven:
mvn clean compile
mvn clean test
mvn clean package
mvn clean install
mvn clean deploy
Обычно перед любой командой добавляют clean, чтобы удалить артефакты предыдущей сборки и избежать проблем с устаревшими файлами.
Maven-зависимости — scope и управление версиями
Зависимости в Maven указываются в секции <dependencies>. Каждая зависимость определяется тройкой GAV (groupId, artifactId, version). Но помимо этого, у зависимости есть параметр scope — область видимости.
Scope определяет, когда и где зависимость доступна:
| Scope | Компиляция | Тесты | Рантайм | Упаковка |
|---|---|---|---|---|
compile |
✓ | ✓ | ✓ | ✓ |
provided |
✓ | ✓ | ✗ | ✗ |
runtime |
✗ | ✓ | ✓ | ✓ |
test |
✗ | ✓ | ✗ | ✗ |
system |
✓ | ✓ | ✗ | ✗ |
compile — используется по умолчанию. Зависимость доступна везде и попадает в финальный артефакт. Пример: библиотека Gson, которая нужна вашему коду.
provided — зависимость доступна при компиляции, но не попадает в артефакт, потому что предполагается, что среда выполнения уже её предоставляет. Пример: API сервлетов в веб-приложении — контейнер Tomcat уже содержит эти классы.
runtime — зависимость не нужна при компиляции, но нужна при выполнении. Пример: JDBC-драйвер — ваш код компилируется к интерфейсу java.sql.Connection, а конкретная реализация подключается только при запуске.
test — зависимость доступна только в тестах. Пример: JUnit — он не нужен в продакшне.
Пример подключения зависимостей
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>
Maven-плагины — расширение возможностей
Maven сам по себе — это лишь каркас. Вся реальная работа выполняется плагинами. Каждая фаза жизненного цикла привязана к одному или нескольким плагинам. Без плагинов Maven не умеет ничего — он даже не может скомпилировать код.
Два самых важных плагина:
- maven-compiler-plugin — компилирует Java-исходники. По умолчанию использует версию Java 1.6, что для современных проектов слишком старо. Рекомендуется явно указывать версию.
- maven-surefire-plugin — запускает单元 тесты. Работает с JUnit 4 и JUnit 5. Автоматически ищет классы тестов по именованным паттернам.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>21</source>
<target>21</target>
<release>21</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
В этом примере мы настроили компилятор на Java 21, указали плагин для запуска тестов и настроили JAR-плагин для создания исполняемого JAR-файла с указанием главного класса.
Совет: Всегда явно указывайте версию maven-compiler-plugin и параметры source/target/release. Без этого Maven будет компилировать ваш код под Java 1.6, и вы получите ошибки при использовании модулей, records, switch-expression и других современных возможностей Java.
Maven-профили
Профили в Maven позволяют создавать различные конфигурации для разных окружений. Например, вы можете иметь профиль для разработки (с H2 базой данных), профиль для тестирования (с PostgreSQL) и профиль для продакшна (с Oracle).
Профили активируются через командную строку с флагом -P:
<profiles>
<profile>
<id>dev</id>
<properties>
<db.url>jdbc:h2:mem:test</db.url>
<db.driver>org.h2.Driver</db.driver>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<db.url>jdbc:postgresql://prod-server:5432/mydb</db.url>
<db.driver>org.postgresql.Driver</db.driver>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
</dependencies>
</profile>
</profiles>
Запуск с профилем:
mvn clean package -Pdev
mvn clean package -Pprod
Профили также могут включать/исключать зависимости, менять переменные окружения, настраивать плагины и даже определять разные исходники для компиляции.
Gradle — гибкость и производительность
Gradle появился в 2012 году как современная замена Maven. Он использует ту же философию «convention over configuration», но добавляет гибкость и значительно лучшую производительность. Gradle использует DAG (Directed Acyclic Graph) для определения порядка выполнения задач и кэширует результаты промежуточных шагов.
Основные преимущества Gradle перед Maven:
- Производительность — инкрементальная сборка, кэширование, демон для поддержки JVM в памяти
- Гибкость — язык конфигурации (Groovy или Kotlin) вместо XML
- Управление зависимостями — более мощный механизм разрешения конфликтов
- Multi-project builds — лучшая поддержка мультипроектных сборок
- Android — является официальным инструментом сборки Android-приложений
build.gradle — Groovy DSL vs Kotlin DSL
Gradle поддерживает два языка конфигурации: Groovy DSL (файл build.gradle) и Kotlin DSL (файл build.gradle.kts). Оба делают одно и то же, но синтаксис отличается.
Groovy DSL более компактный и «скриптовый»:
plugins {
id 'java'
id 'application'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
runtimeOnly 'org.postgresql:postgresql:42.7.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1'
}
application {
mainClass = 'com.example.App'
}
test {
useJUnitPlatform()
}
Kotlin DSL более строгий и с типизацией:
plugins {
java
application
}
group = "com.example"
version = "1.0.0"
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
}
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
runtimeOnly("org.postgresql:postgresql:42.7.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
}
application {
mainClass.set("com.example.App")
}
tasks.test {
useJUnitPlatform()
}
Какой DSL выбрать? Если вы только начинаете, рекомендуем Kotlin DSL — он обеспечивает автодополнение в IDE, проверку типов и более понятные сообщения об ошибках. Groovy DSL проще для скриптов и быстрых экспериментов.
Gradle-задачи (tasks)
Вместо фаз жизненного цикла Maven использует задачи (tasks). Каждая задача — это отдельная единица работы, которую Gradle может выполнить. Задачи связаны между собой зависимостями — Gradle строит граф зависимостей и выполняет задачи в оптимальном порядке.
Основные задачи Gradle:
| Задача | Описание | Аналог Maven |
|---|---|---|
clean |
Удаляет папку build |
clean |
compileJava |
Компилирует основной исходный код | compile |
test |
Запускает单元 тесты | test |
build |
Компилирует, тестирует, упаковывает | package |
fatJar |
Создаёт JAR со всеми зависимостями | Нет аналога (нужен плагин) |
Запуск задач в Gradle:
./gradlew build
./gradlew clean build
./gradlew test
./gradlew fatJar
./gradlew tasks --all
Команда ./gradlew tasks --all покажет полный список доступных задач. Это очень полезно при знакомстве с новым проектом.
Gradle-зависимости — области видимости
Gradle использует конфигурации вместо scope. Каждая зависимость принадлежит к одной или нескольким конфигурациям. Основные конфигурации:
| Конфигурация | Описание | Аналог Maven |
|---|---|---|
implementation |
Доступна при компиляции и рантайме, скрыта от потребителей | compile |
api |
Доступна потребителям (нужен java-library плагин) | compile |
compileOnly |
Доступна только при компиляции | provided |
runtimeOnly |
Доступна только в рантайме | runtime |
testImplementation |
Доступна только в тестах | test |
Важное отличие implementation от api: при использовании implementation зависимость не видна потребителям вашего модуля. Это ускоряет сборку, потому что при изменении зависимой библиотеки нужно перекомпилировать только ваш модуль, а не всех потребителей.
Gradle Wrapper — воспроизводимость сборки
Gradle Wrapper (файлы gradlew, gradlew.bat, gradle/wrapper/gradle-wrapper.properties) — это механизм для обеспечения воспроизводимости сборки. Он гарантирует, что все разработчики и CI/CD системы используют одну и ту же версию Gradle.
Вместо того чтобы требовать установки Gradle на каждом компьютере, Wrapper автоматически скачивает нужную версию при первом запуске. Это решает проблему «у меня работает, у тебя нет».
Создание Wrapper:
gradle wrapper --gradle-version 8.5
./gradlew --version
Файл gradle-wrapper.properties содержит URL для скачивания и контрольную сумму:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Файлы Wrapper должны быть добавлены в Git. Это стандартная практика — каждый, кто клонирует репозиторий, может сразу запустить ./gradlew build без установки Gradle.
Почему Wrapper так важен? Представьте, что вы обновили Gradle до версии 8.5, а ваш коллега всё ещё использует 7.6. Ваши конфигурации могут работать по-разному. Wrapper решает эту проблему — он фиксирует версию Gradle в репозитории и автоматически скачивает её.
Создание fat JAR в Gradle
Fat JAR (или uber JAR) — это JAR-файл, содержащий не только ваш код, но и все зависимости. Это удобно для деплоя — вы копируете один файл и запускаете его командой java -jar app.jar.
В Gradle создание fat JAR реализуется через задачу jar или плагин shadow:
plugins {
id 'java'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.slf4j:slf4j-api:2.0.9'
runtimeOnly 'org.slf4j:slf4j-simple:2.0.9'
}
application {
mainClass = 'com.example.App'
}
jar {
manifest {
attributes 'Main-Class': 'com.example.App'
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
После запуска ./gradlew jar вы получите JAR-файл в папке build/libs/, который можно запустить командой java -jar build/libs/my-application-1.0.0.jar.
Сравнение Maven и Gradle
Оба инструмента отличный выбор, но у каждого есть свои сильные и слабые стороны. Вот подробное сравнение:
| Критерий | Maven | Gradle |
|---|---|---|
| Язык конфигурации | XML | Groovy / Kotlin |
| Производительность | Средняя | Высокая (кэширование,增量) |
| Гибкость | Ограниченная | Высокая |
| Обучаемость | Проще для новичков | Сложнее, но мощнее |
| Экосистема | Очень большая | Большая, растёт |
| Стандарт де-факто | Да | Да (особенно Android) |
| Multi-project | Поддержка | Отличная поддержка |
Если вы начинаете новый проект и не знаете, что выбрать:
- Выберите Maven, если вы новичок, если проект простой, или если команда уже использует Maven.
- Выберите Gradle, если вам нужна гибкость, высокая производительность, или если вы разрабатываете Android-приложение.
- В любом случае — оба инструмента профессиональные и широко используются в индустрии.
Практика: создание Maven-проекта с нуля
Давайте создадим Maven-проект с нуля и подключим к нему JUnit 5 и Lombok. Следуйте инструкциям пошагово.
Шаг 1: Создание структуры проекта
mkdir -p src/main/java/com/example/demo
mkdir -p src/main/resources
mkdir -p src/test/java/com/example/demo
mkdir -p src/test/resources
Шаг 2: Создание pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-project</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<junit.version>5.10.1</junit.version>
<lombok.version>1.18.30</lombok.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.demo.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Шаг 3: Код приложения
package com.example.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private String name;
private int age;
private String email;
public boolean isAdult() {
return age >= 18;
}
public String getGreeting() {
return "Привет, " + name + "!";
}
}
Шаг 4: Главный класс
package com.example.demo;
import java.util.List;
public class App {
public static void main(String[] args) {
List<User> users = List.of(
new User("Алексей", 25, "alexey@mail.ru"),
new User("Мария", 17, "maria@mail.ru"),
new User("Дмитрий", 32, "dmitry@mail.ru"),
new User("Анна", 19, "anna@mail.ru")
);
System.out.println("=== Список пользователей ===");
for (User user : users) {
System.out.println(user.getGreeting());
System.out.println(" Возраст: " + user.getAge());
System.out.println(" Email: " + user.getEmail());
System.out.println(" Совершеннолетний: " + user.isAdult());
System.out.println();
}
long adultCount = users.stream()
.filter(User::isAdult)
.count();
System.out.println("Совершеннолетних: " + adultCount + " из " + users.size());
}
}
Шаг 5: Тест
package com.example.demo;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class UserTest {
@Test
void shouldReturnTrueForAdult() {
User user = new User("Тест", 25, "test@mail.ru");
assertTrue(user.isAdult());
}
@Test
void shouldReturnFalseForMinor() {
User user = new User("Тест", 15, "test@mail.ru");
assertFalse(user.isAdult());
}
@Test
void shouldReturnCorrectGreeting() {
User user = new User("Ольга", 30, "olga@mail.ru");
assertEquals("Привет, Ольга!", user.getGreeting());
}
@Test
void shouldHaveCorrectName() {
User user = new User("Сергей", 40, "sergey@mail.ru");
assertEquals("Сергей", user.getName());
}
@Test
void shouldHaveCorrectEmail() {
User user = new User("Ирина", 22, "irina@mail.ru");
assertEquals("irina@mail.ru", user.getEmail());
}
}
Шаг 6: Запуск сборки и тестов
mvn clean compile
mvn clean test
mvn clean package
java -jar target/demo-project-1.0.0.jar
Вывод программы:
=== Список пользователей ===
Привет, Алексей!
Возраст: 25
Email: alexey@mail.ru
Совершеннолетний: true
Привет, Мария!
Возраст: 17
Email: maria@mail.ru
Совершеннолетний: false
Привет, Дмитрий!
Возраст: 32
Email: dmitry@mail.ru
Совершеннолетний: true
Привет, Анна!
Возраст: 19
Email: anna@mail.ru
Совершеннолетний: true
Совершеннолетних: 3 из 4
Обратите внимание: В pom.xml мы использовали переменные в секции <properties>. Это позволяет задать версии зависимостей в одном месте и использовать их через ${variable}. При обновлении версии нужно изменить только одно значение.
Multi-module проекты
Крупные проекты обычно разбиваются на несколько модулей. Например, интернет-магазин может состоять из модулей: core (модели данных), web (веб-интерфейс), api (REST API), service (бизнес-логика). Это упрощает навигацию, параллельную разработку и повторное использование кода.
В Maven multi-module проект имеет родительский pom.xml и дочерние модули:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-shop</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>service</module>
<module>web</module>
<module>api</module>
</modules>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<junit.version>5.10.1</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
В Gradle multi-module проект использует файл settings.gradle для указания модулей:
rootProject.name = 'my-shop'
include 'core'
include 'service'
include 'web'
include 'api'
Каждый модуль имеет свой build.gradle (или pom.xml), но общие зависимости и настройки выносятся в родительский файл. Это уменьшает дублирование и упрощает обновление версий.
Полезные команды Maven и Gradle
Вот шпаргалка с самыми полезными командами для повседневной работы:
Maven
mvn clean install # Собрать и установить в локальный репозиторий
mvn dependency:tree # Показать дерево зависимостей
mvn dependency:analyze # Анализ неиспользуемых зависимостей
mvn versions:display-dependency-updates # Проверить обновления зависимостей
mvn help:effective-pom # Показать итоговый POM с учётом наследования
mvn archetype:generate # Создать проект из шаблона
mvn -DskipTests package # Собрать без запуска тестов
mvn dependency:resolve # Скачать все зависимости
Gradle
./gradlew build # Полная сборка
./gradlew clean build # Очистка и сборка
./gradlew dependencies # Показать дерево зависимостей
./gradlew test # Запустить тесты
./gradlew fatJar # Создать JAR со всеми зависимостями
./gradlew tasks --all # Все доступные задачи
./gradlew build -x test # Собрать без тестов
./gradlew --rerun-tasks build # Полная пересборка
Типичные ошибки и как их избежать
Ошибка: «Unable to resolve dependency»
Менее не может найти библиотеку. Причины: опечатка в GAV, библиотека удалена из Maven Central, проблемы с сетью. Решение: проверьте GAV, очистите локальный кэш (rm -rf ~/.m2/repository), проверьте подключение к интернету.
Ошибка: «Could not find or load main class»
JAR не содержит манифест с указанием главного класса или главный класс указан неверно. Решение: проверьте конфигурацию maven-jar-plugin или application в Gradle.
Ошибка: «Unsupported class file major version»
Вы компилируете код под более новую версию Java, чем установлена на компьютере. Решение: установите нужную версию JDK или понизьте maven.compiler.release.
Ошибка: «Duplicate class» при сборке fat JAR
При объединении JAR-файлов обнаруживаются одинаковые классы. Решение: используйте duplicatesStrategy = DuplicatesStrategy.EXCLUDE в Gradle или настройте maven-shade-plugin в Maven.
Совет: Если вы столкнулись с конфликтом зависимостей, выполните mvn dependency:tree (Maven) или ./gradlew dependencies (Gradle). Эти команды покажут полное дерево зависимостей с указанием того, какая библиотека тянет какую. Это поможет найти источник конфликта.
Сборка и деплой — полная картина
Давайте посмотрим на полный цикл от написания кода до запуска приложения:
- Разработка — вы пишете Java-код и тесты
- Компиляция —
mvn compileили./gradlew compileJavaпревращает.javaв.class - Тестирование —
mvn testили./gradlew testзапускает все тесты - Упаковка —
mvn packageили./gradlew buildсоздаёт JAR/WAR - Проверка — интеграционные тесты, статический анализ
- Установка —
mvn installили./gradlew publishToMavenLocalделает JAR доступным для других проектов - Деплой —
mvn deployотправляет JAR в корпоративный репозиторий
Этот процесс автоматизируется с помощью CI/CD систем (Jenkins, GitHub Actions, GitLab CI). Инструмент сборки — ключевой компонент конвейера, потому что он обеспечивает воспроизводимость: каждый разработчик и каждая CI-система собирают проект одинаково.
Итоги урока
Итоги урока
- Инструменты сборки (Maven, Gradle) автоматизируют управление зависимостями, компиляцию, тестирование и упаковку
- Maven использует XML (
pom.xml) и философию «convention over configuration» — стандартная структура проекта избавляет от лишней конфигурации - Каждый Maven-проект идентифицируется тройкой GAV: groupId, artifactId, version
- Жизненный цикл Maven включает фазы: clean, compile, test, package, install, deploy
- Scope (compile, provided, test, runtime) определяет, когда зависимость доступна
- Gradle использует язык конфигурации (Groovy или Kotlin) вместо XML и обеспечивает лучшую производительность
- Gradle Wrapper (
gradlew) гарантирует одинаковую версию Gradle у всех участников команды - Fat JAR объединяет код приложения и все зависимости в один исполняемый файл
- Multi-module проекты разбивают крупные приложения на логические модули для удобства разработки
- Команды
dependency:treeиdependenciesпомогают диагностировать конфликты версий
Тест: Инструмент сборки Maven/Gradle
6 вопросов