Создание исполняемого JAR в Maven: включение всех зависимостей

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Java-разработчики, желающие улучшить навыки работы с Maven и сборкой приложений
  • Специалисты по DevOps, заинтересованные в упрощении процесса деплоя Java-приложений
  • Студенты и новички в области программирования, стремящиеся освоить создание самодостаточных JAR-файлов

    Знакомы ли вам ситуации, когда приложение прекрасно работает на вашей машине, но стоит только передать его коллегам или развернуть на другом сервере, как начинаются бесконечные "А у меня этой библиотеки нет" и "ClassNotFoundException"? 🤦‍♂️ Создание исполняемого JAR-файла со всеми зависимостями — это настоящий must-have для Java-разработчика. Это как швейцарский нож в вашем техническом арсенале, который избавляет от головной боли при распространении приложений и гарантирует, что ваш код будет работать везде одинаково. Давайте разберемся, как превратить многофайловый кошмар в элегантный, самодостаточный JAR с помощью Maven.

Хотите освоить Maven и другие инструменты профессиональной Java-разработки? На Курсе Java-разработки от Skypro вы не только изучите теорию, но и создадите реальные проекты с использованием промышленных стандартов. Наши выпускники умеют собирать сложные приложения одним файлом и разворачивать их в любой среде. Присоединяйтесь к сообществу разработчиков, которые решают реальные задачи, а не просто пишут "Hello World". 🚀

Основы создания JAR-файлов с зависимостями в Maven

JAR-файл (Java ARchive) — стандартный формат для упаковки Java-приложений. По умолчанию Maven собирает JAR, содержащий только ваши классы, но не включает зависимости. Это создает очевидную проблему: такой JAR не будет работать самостоятельно.

Существует несколько типов JAR-файлов, которые решают эту проблему:

  • Fat/Uber JAR — содержит все зависимости, распакованные в корне архива
  • Shaded JAR — включает переименованные (для избежания конфликтов) зависимости
  • JAR с манифестом — содержит ссылки на внешние зависимости в манифесте
  • JAR с зависимостями — содержит оригинальный JAR и все зависимости в отдельных директориях

Для создания исполняемого JAR необходимо решить две ключевые задачи:

  1. Включить все зависимости в один файл
  2. Указать главный класс для запуска (точку входа)

Maven предлагает несколько плагинов для решения этих задач. Наиболее популярные:

Плагин Тип JAR Особенности Сложность настройки
maven-shade-plugin Shaded JAR Переименование пакетов, трансформации классов Средняя
maven-assembly-plugin JAR с зависимостями Гибкие архивные форматы Низкая
maven-jar-plugin + maven-dependency-plugin JAR с манифестом Зависимости отдельно Высокая
spring-boot-maven-plugin Executable JAR Специально для Spring Boot Низкая

Алексей Громов, DevOps-инженер В одном из наших проектов мы столкнулись с настоящим кошмаром при деплое Java-приложения. Каждый раз при обновлении приходилось следить за десятками библиотек, часто возникали конфликты версий и неработающие компоненты. После многочасовой отладки очередного ночного деплоя я решил, что пора положить этому конец. Я интегрировал maven-shade-plugin в нашу сборку и настроил создание единого JAR-файла. Поначалу было несколько проблем с конфликтами имён, но после правильной настройки трансформеров всё встало на места. Теперь наш CI/CD пайплайн создаёт один самодостаточный JAR, который мы просто копируем на сервер и запускаем. Время деплоя сократилось с 40-50 минут до 3-5, и что еще важнее — мы забыли о бессонных ночах из-за неработающих зависимостей. Один файл — одна команда для запуска, и это работает везде.

Пошаговый план для смены профессии

Настройка pom.xml для упаковки всех зависимостей

Центральный файл любого Maven-проекта — pom.xml. Именно в нём мы определяем, как собирать наше приложение. Для создания JAR с зависимостями необходимо правильно настроить секцию <build> и добавить соответствующие плагины. 🛠️

Базовая структура pom.xml должна содержать следующие ключевые элементы:

  • Координаты проекта (groupId, artifactId, version)
  • Зависимости (dependencies)
  • Конфигурация сборки (build)
  • Настройки плагинов (plugins)

Рассмотрим пример базового pom.xml файла:

xml
Скопировать код
<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-app</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Здесь будут настройки плагинов для сборки JAR -->
</plugins>
</build>
</project>

Для успешной сборки JAR с зависимостями важно учитывать следующие аспекты:

Элемент pom.xml На что влияет Рекомендации
<dependencies> Набор библиотек в JAR Указывайте точные версии, избегайте конфликтующих зависимостей
<scope> у зависимостей Включение в финальный JAR 'provided' и 'test' не попадут в JAR
<maven.compiler.source/target> Совместимость с Java Выбирайте версию исходя из требований к среде запуска
<mainClass> Точка входа в приложение Обязательна для исполняемого JAR

Чтобы Maven знал, какой класс должен быть запущен при выполнении JAR-файла, необходимо указать mainClass. Это делается в конфигурации соответствующего плагина. Также важно убедиться, что этот класс имеет метод public static void main(String[] args).

Maven-shade-plugin: конфигурация для исполняемого JAR

Maven Shade Plugin — один из самых мощных и гибких инструментов для создания исполняемых JAR-файлов. Он не просто копирует зависимости в архив, а может выполнять сложные трансформации, включая переименование пакетов для предотвращения конфликтов. 🔄

Добавление плагина в pom.xml выглядит следующим образом:

xml
Скопировать код
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Ключевые элементы конфигурации:

  • <phase>package</phase> — указывает, что плагин выполняется на фазе package жизненного цикла Maven
  • <mainClass> — полное имя класса с методом main
  • <transformers> — определяют как обрабатываются ресурсы при объединении

Shade плагин предоставляет ряд дополнительных опций для тонкой настройки:

xml
Скопировать код
<configuration>
<!-- Создаёт минимизированную версию JAR -->
<minimizeJar>true</minimizeJar>

<!-- Фильтры для включения/исключения файлов -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>

<!-- Изменяет имя выходного файла -->
<finalName>uber-${project.artifactId}-${project.version}</finalName>

<!-- Прикрепляет классификатор к имени артефакта -->
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>with-dependencies</shadedClassifierName>

<!-- Трансформеры для обработки ресурсов -->
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
<!-- Объединяет файлы services/ -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>

Для проектов с большим количеством зависимостей могут возникать конфликты из-за дублирующихся файлов. Shade Plugin позволяет решать это с помощью переименования пакетов:

xml
Скопировать код
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>shaded.org.apache.commons</shadedPattern>
</relocation>
</relocations>

После настройки плагина сборка производится стандартной командой:

mvn clean package

Maven создаст два JAR-файла в директории target:

  • my-app-1.0-SNAPSHOT.jar — стандартный JAR без зависимостей
  • uber-my-app-1.0-SNAPSHOT.jar (или как указано в <finalName>) — исполняемый JAR со всеми зависимостями

Альтернативные плагины для сборки JAR с зависимостями

Maven-shade-plugin — не единственный способ создать исполняемый JAR. Существуют альтернативные плагины с разными преимуществами и ограничениями. Выбор зависит от конкретных требований проекта. 🧩

Максим Петров, Lead Java Developer На моем предыдущем проекте мы использовали maven-assembly-plugin для упаковки микросервисов. Всё работало отлично, пока мы не начали интегрировать сервисы с технологиями, использующими Service Provider Interface (SPI), такими как JDBC-драйверы и логгеры. Неожиданно приложение стало терять некоторые драйверы и другие SPI-компоненты. Оказалось, что assembly-plugin просто копирует файлы, не объединяя содержимое одноименных файлов в директориях META-INF/services, критичных для SPI. Мы перешли на maven-shade-plugin с настроенным ServicesResourceTransformer. Это потребовало переписывания части конфигурации сборки, но полностью решило проблему — все SPI-компоненты стали корректно регистрироваться и работать. Дополнительным бонусом стала возможность переименования потенциально конфликтующих пакетов с помощью relocations. Этот случай научил меня тщательнее выбирать инструменты сборки исходя из всех аспектов приложения, а не только базовой функциональности.

Рассмотрим основные альтернативы maven-shade-plugin:

1. Maven Assembly Plugin

Этот плагин хорошо подходит для простых случаев и позволяет создавать различные типы дистрибутивов.

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

2. Maven JAR Plugin + Dependency Plugin

Комбинация этих плагинов создаёт JAR с манифестом, ссылающимся на отдельные зависимости. Это увеличивает размер дистрибутива, но может быть полезно при частом обновлении только основного кода.

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

3. Spring Boot Maven Plugin

Специализированный плагин для Spring Boot приложений. Автоматически создаёт исполняемый JAR с корректной структурой для загрузчика Spring Boot.

xml
Скопировать код
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.9</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

Сравнение плагинов по ключевым параметрам:

Функция / Плагин shade assembly jar + dependency spring-boot
Объединение SPI файлов
Переименование пакетов
Минимизация JAR
Один файл
Настраиваемые дистрибутивы ⚠️ ⚠️ ⚠️

При выборе плагина учитывайте:

  • Сложность проекта и его зависимостей
  • Наличие SPI-компонентов (JDBC, логирование и т.д.)
  • Потенциальные конфликты зависимостей
  • Требования к размеру итогового JAR
  • Требования к дистрибутиву (один файл или архив с вспомогательными файлами)

Тестирование и запуск собранного JAR-файла

После успешной сборки JAR-файла критически важно протестировать его работоспособность, особенно в среде, отличной от вашей разработческой машины. Правильно собранный JAR должен запускаться простой командой и работать идентично на любой системе с установленной Java. 🚀

Основные шаги тестирования JAR-файла:

  1. Запуск в другой директории — убедитесь, что JAR не зависит от локальных путей
  2. Проверка на чистой машине — без установленных зависимостей
  3. Тестирование с разными версиями Java — для выявления проблем совместимости
  4. Проверка логов на ошибки — некоторые проблемы могут быть не очевидны

Запуск JAR-файла осуществляется командой:

java -jar target/your-app-with-dependencies.jar

Для более тонкой настройки запуска можно использовать JVM-опции:

java -Xms512m -Xmx1024m -jar target/your-app-with-dependencies.jar

Для приложений с расширенной конфигурацией можно передавать аргументы командной строки:

java -jar target/your-app-with-dependencies.jar --config=prod.properties

Часто возникающие проблемы и их решения:

  • ClassNotFoundException — не включена нужная зависимость, проверьте scope в pom.xml
  • NoClassDefFoundError — конфликт версий или неправильно настроен плагин сборки
  • Отсутствие ресурсов — проверьте, что ресурсы правильно включены в JAR
  • "no main manifest attribute" — не указан mainClass в конфигурации плагина
  • UnsatisfiedLinkError — проблемы с нативными библиотеками, которые должны быть доступны в системе

Для проверки содержимого JAR-файла можно использовать команды:

jar tf target/your-app-with-dependencies.jar

Или для просмотра манифеста:

jar xf target/your-app-with-dependencies.jar META-INF/MANIFEST.MF
cat META-INF/MANIFEST.MF

Для автоматизации проверки JAR-файла создайте скрипт тестирования, который:

  1. Запускает JAR в чистой директории
  2. Выполняет базовый набор операций
  3. Проверяет ожидаемые результаты
  4. Отлавливает ошибки и исключения

Пример простого bash-скрипта для тестирования:

Bash
Скопировать код
#!/bin/bash
# Создаем временную директорию
TEST_DIR=$(mktemp -d)
cp target/your-app-with-dependencies.jar $TEST_DIR/
cd $TEST_DIR

# Запускаем с тестовыми параметрами и перенаправляем вывод
java -jar your-app-with-dependencies.jar --test > output.log 2>&1

# Проверяем результат
if grep -q "Successfully completed" output.log; then
echo "Test passed! ✓"
exit 0
else
echo "Test failed! ✗"
cat output.log
exit 1
fi

Помните, что хорошая практика DevOps — включать подобные тесты в ваш CI/CD-пайплайн, чтобы автоматически проверять каждую новую версию JAR-файла перед релизом.

Создание исполняемого JAR с зависимостями — это больше, чем просто техническое решение. Это философия упрощения деплоя и распространения приложений. Когда у вас есть один самодостаточный файл, исчезают проблемы с несовместимостью версий зависимостей, упрощается автоматизация и развертывание. Будь то микросервис в корпоративной инфраструктуре или инструмент с открытым исходным кодом — правильно упакованный JAR превращается из головной боли в инструмент, которым можно гордиться. Применяйте описанные методы и пусть ваши Java-приложения работают везде так же хорошо, как и на вашей машине. 🚀

Загрузка...