[Программирование, Java] Создание самодостаточных исполняемых JAR (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
21-Июн-2021 19:33


Когда программное приложение выходит за пределы десятка строк кода, вам, вероятно, следует разделить код на несколько классов. На этом этапе встает вопрос о том, как их распределить. В Java классическим форматом является Java-архив, более известный как JAR. Но реальные программы, вероятно, зависят от других JAR.Цель этой статьи — описать способы создания самодостаточных исполняемых (self-contained executable) JAR, также известных как uber-JAR или fat JAR.Что такое самодостаточный JAR?JAR — это просто набор файлов классов. Чтобы быть исполняемым, его файл META-INF/MANIFEST.MF должен указывать на класс, реализующий метод main(). Это делается с помощью атрибута Main-Class. Вот пример:
Main-Class: path.to.MainClass
У MainClass метод static main(String…​ args)Работа с classpathБольшинство программ зависит от существующего кода. Java предоставляет концепцию classpath. Путь класса — это список элементов пути, который будет просматриваться во время выполнения программы, что поможет найти зависимый код. При запуске классов Java вы определяете classpath с помощью параметра командной строки -cp:
java -cp lib/one.jar;lib/two.jar;/var/lib/three.jar path.to.MainClass
Время выполнения Java создает classpath, объединяя все классы из всех связанных JAR и добавляя при этом главный класс.Новые проблемы возникают при дистрибуции JAR, которые зависят от других JAR:1.Вам необходимо определить те же библиотеки в той же версии.2. Что еще более важно, аргумент -cp не работает с JAR. Чтобы ссылаться на другие JAR, classpath должен быть задан в манифесте JAR через атрибут Class-Path:
Class-Path: lib/one.jar;lib/two.jar;/var/lib/three.jar
3. По этой причине вам необходимо поместить JAR в то же место, относительное или абсолютное, в целевую файловую систему в соответствии с манифестом. Это означает, что сначала нужно открыть JAR и прочитать манифест.Одним из способов решения этих проблем является создание уникальной единицы развертывания, которая содержит классы из всех JAR и может быть распространена как один артефакт. Существует несколько вариантов создания таких JAR: Плагин Apache Assembly 
Assembly Plugin для Apache Maven позволяет разработчикам объединять результаты проекта в единый распространяемый архив, который также содержит зависимости, модули, документацию сайта и другие файлы.— Плагин Apache Maven Assembly 
Одним из правил проектирования Maven является создание одного артефакта на проект. Существуют исключения, например, артефакты Javadocs и артефакты исходного кода, но в целом, если вам нужно несколько артефактов, вам нужно создать один проект для каждого артефакта. Идея плагина Assembly заключается в том, чтобы обойти это правило.Плагин Assembly полагается на специальный конфигурационный файл assembly.xml. Он позволяет вам выбирать, какие файлы будут включены в артефакт. Обратите внимание, что конечный артефакт не обязательно должен быть JAR: конфигурационный файл позволяет вам выбирать между доступными форматами, например, zip, war и т.д.Плагин регулирует общие случаи использования, предоставляя предварительно определенные сборки (assemblies). Среди них - распространение самодостаточных JAR. Конфигурация выглядит следующим образом:pom.xml
<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
      <manifest>
        <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass>
      </manifest>
    </archive>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>single</goal>
      </goals>
      <phase>package</phase>
    </execution>
  </executions>
</plugin>
  • Ссылайтесь на предварительно определенную самодостаточную конфигурацию JAR
  • Установите главный класс для исполнения
  • Выполните single <goal>
  • Привяжите <goal> к package после формирование исходного JAR 
Запуск mvn package дает два артефакта:
  • <name>-<version>.jar
  • <name>-<version>-with-dependencies.jar
Первый JAR имеет то же содержимое, что и тот, который был бы создан без плагина. Второй — это самодостаточный JAR. Вы можете выполнить его следующим образом:
java -jar target/executable-jar-0.0.1-SNAPSHOT.jar
В зависимости от проекта он может выполняться успешно... или нет. Например, в примере проекта Spring Boot он не работает со следующим сообщением:
%d [%thread] %-5level %logger - %msg%n java.lang.IllegalArgumentException:
  No auto configuration classes found in META-INF/spring.factories.
  If you are using a custom packaging, make sure that file is correct.
Причина в том, что разные JAR предоставляют разные ресурсы по одному и тому же пути, как например с META-INF/spring.factories.Зачастую плагин следует стратегии "побеждает последний записавший". Порядок основывается на имени JAR.С помощью Assembly вы можете исключить ресурсы, но не объединять их. Если вам нужно объединить ресурсы, вы, вероятно, захотите использовать плагин Apache Shade.Плагин Apache Shade Плагин Assembly является общим; плагин Shade ориентирован исключительно на задачу создания самодостаточных JAR.
Этот плагин предоставляет возможность упаковать артефакт в uber-jar, включая его зависимости, и оттенить — т.е. переименовать — пакеты некоторых зависимостей. — Плагин Apache Maven Shade 
Плагин основан на концепции преобразователей: каждый преобразователь отвечает за работу с одним типом ресурсов. Преобразователь может копировать ресурс как есть, добавлять статическое содержимое, объединять его с другими и т.д.Хотя вы можете разработать свой преобразователь, плагин предоставляет набор готовых преобразователей:
Конфигурация плагина Shade к приведенному выше Assembly выглядит следующим образом:pom.xml
<plugin>
  <artifactId>maven-shade-plugin</artifactId>
  <executions>
    <execution>
      <id>shade</id>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass>
            <manifestEntries>
              <Multi-Release>true</Multi-Release>
            </manifestEntries>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>
  • shade привязан к фазе package по умолчанию
  • Этот преобразователь предназначен для генерации файлов манифеста
  • Выполните ввод Main-Class
  • Настройте финальный JAR так, чтобы он был многорелизным JAR. Это необходимо в случае, когда любой из исходных JAR является многорелизным JAR
Запуск mvn package дает два артефакта:
  • <name>-<version>.jar: самодостаточный исполняемый JAR
  • original-<name>-<version>.jar: "обычный" JAR без встроенных зависимостей
При работе с проектом, взятым за образец, финальный исполняемый файл все еще не работает так, как ожидалось. Действительно, во время сборки появляется множество предупреждений о дублировании ресурсов. Два из них мешают корректной работе проекта. Чтобы правильно их объединить, нам нужно посмотреть на их формат:
  • META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat: этот Log4J2 файл содержит предварительно скомпилированные данные плагина Log4J2. Он закодирован в двоичном формате, и ни один из готовых преобразователей не может объединить такие файлы. Тем не менее, случайный поиск показывает, что кто-то уже занимался этой проблемой и выпустил преобразовательдля работы с объединением.
  • META-INF/spring.factories: эти файлы, специфичные для Spring, они имеют формат "один ключ/много значений". Поскольку они текстовые, ни один готовый преобразователь не может корректно объединить их. Однако разработчики Spring предоставляют такую возможность (и многое другое) в своем плагине.
Чтобы настроить эти преобразователи, нам нужно добавить вышеуказанные библиотеки в качестве зависимостей к плагину Shade:
<plugin>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.2.4</version>
  <executions>
    <execution>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass>
            <manifestEntries>
              <Multi-Release>true</Multi-Release>
            </manifestEntries>
          </transformer>
          <transformer implementation="com.github.edwgiz.maven_shade_plugin.log4j2_cache_transformer.PluginsCacheFileTransformer" />
          <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
            <resource>META-INF/spring.factories</resource>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>com.github.edwgiz</groupId>
      <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
      <version>2.14.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>2.4.1</version>
    </dependency>
  </dependencies>
</plugin>
pom.xml
  • Объедините Log4J2 .dat файлы 
  • Объедините файлы /META-INF/spring.factories
  • Добавьте необходимый код для преобразователей
Эта конфигурация работает! Тем не менее, есть оставшиеся предупреждения:
  • Манифесты
  • Лицензии, предупреждения и схожие файлы
  • Spring Boot файлы, например, spring.handlers, spring.schemas и spring.tooling
  • Файлы Spring Boot-Kotlin, например, spring-boot.kotlin_module, spring-context.kotlin_module, и так далее.
  • Файлы конфигурации Service Loader
  • Файлы JSON 
Вы можете добавить и настроить дополнительные преобразователи для устранения оставшихся предупреждений. В целом, весь процесс требует глубокого понимания каждого вида ресурсов и процесса работы с ними.Плагин Spring Boot Плагин Spring Boot использует совершенно другой подход. Он не объединяет ресурсы из JAR по отдельности; он добавляет зависимые JAR по мере их нахождения внутри uber JAR. Для загрузки классов и ресурсов он предоставляет специальный механизм загрузки классов. Очевидно, что он предназначен для проектов Spring Boot.Настройка плагина Spring Boot проста:pom.xml
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>2.4.1</version>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
</plugin>
Давайте проверим структуру финального JAR:
/
|__ BOOT-INF
|    |__ classes
|    |__ lib
|__ META-INF
|    |__ MANIFEST.MF
|__ org
      |__ springframework
           |__ loader
  • Скомпилированные классы проекта
  • JAR зависимости
  • Загрузка классов в Spring Boot
Вот выдержка из манифеста по образцу проекта:MANIFEST.MF
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: ch.frankel.blog.executablejar.ExecutableJarApplication
Как вы можете видеть, главный класс является специфичным классом Spring Boot, в то время как "настоящий" главный класс упоминается в другой записи.Для получения дополнительной информации о структуре JAR, пожалуйста, ознакомьтесь со справочной документацией.Заключение В этой статье мы описали 3 различных способа создания самодостаточных исполняемых JAR:
  • Assembly хорошо подходит для простых проектов
  • Когда проект становится более сложным и вам нужно работать с дублирующимися файлами, используйте Shade
  • Наконец, для проектов Spring Boot лучше всего использовать специальный плагин.
Полный исходный код этой статьи можно найти на Githubв формате Maven.Материалы для дополнительного изучения:
Что такое «хороший код» — это во многом спорная тема. Кто-то скажет, что если код работает, значит он достаточно хорош. Кто-то обязательно добавит, что код должен быть легок в понимании и сопровождении. А кто-то добавит, что код еще обязательно должен быть быстрым. Об этом уже много написано и сказано. Что же, давайте еще раз поговорим на эту интересную и холиварную тему. Регистрируйтесь на онлайн-интенсивПеревод подготовлен в рамках курса"Java Developer. Basic"

===========
Источник:
habr.com
===========

===========
Автор оригинала: Nicolas Fränkel
===========
Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_java, #_java, #_assembly, #_jar, #_shade, #_maven, #_spring_boot, #_horoshij_kod (хороший код), #_blog_kompanii_otus (
Блог компании OTUS
)
, #_programmirovanie (
Программирование
)
, #_java
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 21-Сен 18:44
Часовой пояс: UTC + 5