Перейти к основному содержимому

Тонкие JAR-файлы с Spring Boot

· 8 мин. чтения

1. Введение

В этом руководстве мы рассмотрим, как собрать проект Spring Boot в тонкий файл JAR, используя проект spring-boot-thin-launcher .

Spring Boot известен своими «толстыми» развертываниями JAR, где один исполняемый артефакт содержит как код приложения, так и все его зависимости.

Boot также широко используется для разработки микросервисов. Иногда это может противоречить подходу «толстого JAR», потому что повторное включение одних и тех же зависимостей во многие артефакты может стать важной тратой ресурсов.

2. Предпосылки

В первую очередь нам, конечно же, нужен проект Spring Boot. В этой статье мы рассмотрим сборки Maven и сборки Gradle в их наиболее распространенных конфигурациях.

Невозможно охватить все системы сборки и конфигурации сборки, но, надеюсь, мы рассмотрим достаточно общих принципов, чтобы вы могли применить их к своей конкретной настройке.

2.1. Проекты Мавен

В проекте Boot, созданном с помощью Maven, у нас должен быть настроен плагин Spring Boot Maven в файле pom.xml нашего проекта , его родительском элементе или одном из его предков:

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

Версия зависимостей Spring Boot обычно определяется с помощью спецификации или наследования от родительской POM, как в нашем эталонном проекте:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/>
</parent>

2.2. Gradle проекты

В проекте Boot, созданном с помощью Gradle, у нас будет плагин Boot Gradle:

buildscript {
ext {
springBootPlugin = 'org.springframework.boot:spring-boot-gradle-plugin'
springBootVersion = '2.4.0'
}
repositories {
mavenCentral()
}
dependencies {
classpath("${springBootPlugin}:${springBootVersion}")
}
}

// elided

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

springBoot {
mainClassName = 'com.foreach.DemoApplication'
}

Обратите внимание, что в этой статье мы будем рассматривать только проекты Boot 2.x и более поздних версий. Thin Launcher также поддерживает более ранние версии, но для него требуется немного другая конфигурация Gradle, которую мы опускаем для простоты. Пожалуйста, посмотрите на домашнюю страницу проекта для более подробной информации.

3. Как создать тонкий JAR?

Spring Boot Thin Launcher — это небольшая библиотека, которая считывает зависимости артефакта из файла, входящего в состав самого архива, загружает их из репозитория Maven и, наконец, запускает основной класс приложения.

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

Конечно, все немного сложнее, чем наше упрощенное объяснение; мы подробно обсудим некоторые темы позже в этой статье.

4. Основное использование

Давайте теперь посмотрим, как создать «тонкий» JAR из нашего обычного приложения Spring Boot.

Мы запустим приложение с помощью обычного java -jar <my-app-1.0.jar> с необязательными дополнительными аргументами командной строки, которые управляют Thin Launcher. Мы увидим пару из них в следующих разделах; домашняя страница проекта содержит полный список.

4.1. Проекты Мавен

В проекте Maven мы должны изменить объявление плагина Boot (см. раздел 2.1), чтобы включить зависимость от пользовательского «тонкого» макета:

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- The following enables the "thin jar" deployment option. -->
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.11.RELEASE</version>
</dependency>
</dependencies>
</plugin>

Средство запуска будет считывать зависимости из файла pom.xml , который Maven хранит в сгенерированном JAR -файле в каталоге META-INF/maven .

Сборку будем производить как обычно, например, с помощью mvn install .

Если мы хотим иметь возможность создавать как тонкие, так и толстые сборки (например, в проекте с несколькими модулями), мы можем объявить пользовательский макет в специальном профиле Maven.

4.2. Maven и зависимости: thin.properties

Мы также можем заставить Maven сгенерировать файл thin.properties в дополнение к pom.xml . В этом случае файл будет содержать полный список зависимостей, включая транзитивные, и лаунчер предпочтет его pom.xml .

Mojo (плагин) для этого — spring-boot-thin-maven-plugin:properties, и по умолчанию он выводит файл thin.properties в src/main/resources/META-INF , но мы можем указать его местоположение с помощью свойство thin.output :

$ mvn org.springframework.boot.experimental:spring-boot-thin-maven-plugin:properties -Dthin.output=.

Обратите внимание, что выходной каталог должен существовать для достижения цели, даже если мы сохранили каталог по умолчанию.

4.3. Gradle проекты

Вместо этого в проекте Gradle мы добавляем специальный плагин:

buildscript {
ext {
//...
thinPlugin = 'org.springframework.boot.experimental:spring-boot-thin-gradle-plugin'
thinVersion = '1.0.11.RELEASE'
}
//...
dependencies {
//...
classpath("${thinPlugin}:${thinVersion}")
}
}

//elided

apply plugin: 'maven'
apply plugin: 'org.springframework.boot.experimental.thin-launcher'

Чтобы получить тонкую сборку, мы скажем Gradle выполнить задачу thinJar :

~/projects/foreach/spring-boot-gradle $ ./gradlew thinJar

4.4. Gradle и зависимости: pom.xml

В примере кода в предыдущем разделе мы объявили подключаемый модуль Maven в дополнение к Thin Launcher (а также подключаемые модули Boot и Dependency Management, которые мы уже видели в разделе «Предварительные требования»).

Это связано с тем, что, как и в случае с Maven, который мы видели ранее, артефакт будет содержать и использовать файл pom.xml , в котором перечисляются зависимости приложения. Файл pom.xml создается задачей с именем thinPom , которая является неявной зависимостью от любой задачи jar.

Мы можем настроить сгенерированный файл pom.xml с помощью специальной задачи. Здесь мы просто воспроизведем то, что тонкий плагин уже делает автоматически:

task createPom {
def basePath = 'build/resources/main/META-INF/maven'
doLast {
pom {
withXml(dependencyManagement.pomConfigurer)
}.writeTo("${basePath}/${project.group}/${project.name}/pom.xml")
}
}

Чтобы использовать наш пользовательский файл pom.xml , мы добавляем указанную выше задачу в зависимости задачи jar:

bootJar.dependsOn = [createPom]

4.5. Gradle и зависимости: thin.properties

Мы также можем заставить Gradle сгенерировать файл thin.properties, а не pom.xml , как мы делали ранее с Maven.

Задача, создающая файл thin.properties , называется thinProperties и по умолчанию не используется. Мы можем добавить его как зависимость задачи jar:

bootJar.dependsOn = [thinProperties]

5. Хранение зависимостей

Весь смысл тонких банок в том, чтобы избежать связывания зависимостей с приложением. Однако зависимости не исчезают волшебным образом, они просто сохраняются в другом месте.

В частности, Thin Launcher использует инфраструктуру Maven для разрешения зависимостей, поэтому:

  1. он проверяет локальный репозиторий Maven, который по умолчанию лежит в ~/.m2/repository , но может быть перемещен в другое место;
  2. затем он загружает недостающие зависимости из Maven Central (или любого другого настроенного репозитория);
  3. наконец, он кэширует их в локальном репозитории, чтобы не загружать их снова при следующем запуске приложения.

Конечно, фаза загрузки — медленная и подверженная ошибкам часть процесса, потому что для этого требуется доступ к Maven Central через Интернет или доступ к локальному прокси-серверу, а все мы знаем, насколько эти вещи обычно ненадежны.

К счастью, существуют различные способы развертывания зависимостей вместе с приложениями, например, в предварительно упакованном контейнере для облачного развертывания.

5.1. Запуск приложения для разогрева

Самый простой способ кэширования зависимостей — выполнить прогрев приложения в целевой среде. Как мы видели ранее, это приведет к загрузке и кэшированию зависимостей в локальном репозитории Maven. Если мы запустим более одного приложения, репозиторий в конечном итоге будет содержать все зависимости без дубликатов.

Поскольку запуск приложения может иметь нежелательные побочные эффекты, мы также можем выполнить «пробный запуск», который только разрешает и загружает зависимости без запуска какого-либо пользовательского кода:

$ java -Dthin.dryrun=true -jar my-app-1.0.jar

Обратите внимание, что в соответствии с соглашениями Spring Boot мы можем установить свойство -Dthin.dryrun также с аргументом командной строки –thin.dryrun для приложения или с системным свойством THIN_DRYRUN . Любое значение, кроме false , заставит Thin Launcher выполнить пробный запуск.

5.2. Упаковка зависимостей во время сборки

Другой вариант — собрать зависимости во время сборки, не объединяя их в JAR. Затем мы можем скопировать их в целевую среду в рамках процедуры развертывания.

Как правило, это проще, поскольку нет необходимости запускать приложение в целевой среде. Однако, если мы развертываем несколько приложений, нам придется объединить их зависимости либо вручную, либо с помощью скрипта.

Формат, в котором Thin Plugin для Maven и Gradle упаковывает зависимости во время сборки, такой же, как и в локальном репозитории Maven:

root/
repository/
com/
net/
org/
...

На самом деле мы можем указать приложению, использующему Thin Launcher, любой такой каталог (включая локальный репозиторий Maven) во время выполнения с помощью свойства thin.root :

$ java -jar my-app-1.0.jar --thin.root=my-app/deps

Мы также можем безопасно объединить несколько таких каталогов, скопировав их один поверх другого, получив таким образом репозиторий Maven со всеми необходимыми зависимостями.

5.3. Упаковка зависимостей с помощью Maven

Чтобы Maven упаковал для нас зависимости, мы используем цель разрешения spring-boot-thin-maven-plugin. Мы можем вызвать его вручную или автоматически в нашем pom.xml:

<plugin>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-maven-plugin</artifactId>
<version>${thin.version}</version>
<executions>
<execution>
<!-- Download the dependencies at build time -->
<id>resolve</id>
<goals>
<goal>resolve</goal>
</goals>
<inherited>false</inherited>
</execution>
</executions>
</plugin>

После сборки проекта мы найдем каталог target/thin/root/ со структурой, которую мы обсуждали в предыдущем разделе.

5.4. Упаковка зависимостей с помощью Gradle

Если мы используем Gradle с подключаемым модулем тонкого запуска , вместо этого у нас будет доступна задача thinResolve . Задача сохранит приложение и его зависимости в каталоге build/thin/root/ , аналогично плагину Maven из предыдущего раздела:

$ gradlew thinResolve

6. Выводы и дальнейшее чтение

В этой статье мы рассмотрели, как сделать нашу тонкую банку. Мы также увидели, как использовать инфраструктуру Maven для загрузки и хранения их зависимостей.

На домашней странице тонкого модуля запуска есть еще несколько руководств HOW-TO для таких сценариев, как облачное развертывание в Heroku, а также полный список поддерживаемых аргументов командной строки.

Реализацию всех примеров и фрагментов кода Maven можно найти в проекте GitHub — как проекте Maven, поэтому его должно быть легко импортировать и запускать как есть.

Точно так же все примеры Gradle относятся к этому проекту GitHub .