Avatar ·

Как создать исполняемый JAR-файл со всеми зависимостями в Maven?

Всем привет! Я начинающий Java-разработчик и столкнулся с проблемой при сборке проекта с помощью Maven. У меня есть простое консольное приложение, которое использует библиотеку Apache Commons CSV для чтения CSV-файлов. Внутри IDE (IntelliJ IDEA) всё работает отлично. Но когда я пытаюсь собрать исполняемый JAR-файл с помощью `mvn package`, полученный архив не запускается. Вот мой файл `pom.xml`: ```xml 4.0.0 com.example csv-reader 1.0 jar org.apache.commons commons-csv 1.9.0 ``` И мой главный класс `Main.java`: ```java import org.apache.commons.csv.*; public class Main { public static void main(String[] args) throws Exception { System.out.println("Программа запущена!"); // работа с CSV } } ``` Когда я запускаю `java -jar target/csv-reader-1.0.jar`, получаю ошибку: ``` Error: Could not find or load main class com.example.Main ``` Если я добавляю `maven-jar-plugin` с указанием главного класса, то появляется другая ошибка: ``` Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/csv/CSVParser ``` Я понимаю, что зависимости не включаются в JAR. Как правильно собрать один "толстый" JAR, который будет содержать все нужные библиотеки и корректно запускаться? Пробовал разные плагины, но пока ничего не вышло. Помогите, пожалуйста! <<>>

Решение: сборка "толстого" JAR с помощью Maven Shade Plugin

Проблема в том, что стандартная команда mvn package создаёт обычный JAR, который содержит только ваш код и ресурсы. Зависимости (например, Apache Commons CSV) остаются отдельными JAR-файлами в локальном репозитории Maven. Чтобы создать единый исполняемый архив, нужно использовать специальный плагин для сборки "толстого" (fat/uber) JAR.

Лучший инструмент для этой задачи — Maven Shade Plugin. Он не только объединяет все зависимости в один JAR, но и решает проблему конфликтов классов (если разные библиотеки содержат одинаковые файлы).

Пошаговая инструкция

Шаг 1: Добавьте плагин в секцию <build><plugins> вашего pom.xml:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>csv-reader</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-csv</artifactId>
      <version>1.9.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <!-- Указываем, какой класс является главным -->
          <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
              <mainClass>com.example.Main</mainClass>
            </transformer>
          </transformers>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Шаг 2: Убедитесь, что ваш основной класс находится в правильном пакете. В вашем примере класс Main находится в пакете по умолчанию (без package ...). Для production-кода так делать не рекомендуется. Лучше добавить пакет:

package com.example;

import org.apache.commons.csv.*;

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("Программа запущена!");
        // Ваш код
    }
}

Шаг 3: Выполните сборку:

mvn clean package

После этого в папке target появятся два файла:

  • csv-reader-1.0.jar — обычный JAR (без зависимостей), его можно игнорировать.
  • csv-reader-1.0-shaded.jar — "толстый" JAR со всеми зависимостями. Именно его нужно запускать.

Запуск:

java -jar target/csv-reader-1.0-shaded.jar

Важные моменты:

  • Плагин автоматически создаёт манифест с указанием главного класса.
  • Если у вас есть ресурсы (например, файлы конфигурации) в зависимостях, плагин объединит их корректно.
  • При необходимости можно настроить <filters> для исключения ненужных файлов (например, лицензий или .class из тестов).

Этот подход считается стандартом для Java-проектов на Maven. Он простой, надёжный и поддерживается сообществом.

<<>>

Альтернатива: Maven Assembly Plugin

Если вам не нужны продвинутые функции Shade (например, переименование классов для избежания конфликтов), можно использовать более простой Maven Assembly Plugin. Он также создаёт "толстый" JAR, но работает иначе — просто распаковывает все зависимости в один архив.

Пример конфигурации для pom.xml:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <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>

Сборка: mvn clean package. Результат — файл с суффиксом -jar-with-dependencies.jar.

Недостатки по сравнению с Shade:

  • Не решает конфликты классов (если две библиотеки содержат одинаковые файлы, один из них будет перезаписан).
  • Не объединяет файлы META-INF (например, ServiceLoader-файлы), что может сломать некоторые библиотеки.

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