Переход от Spring Boot 3.2 к 3.3 принес изменения в процесс распаковки JAR и запуск приложения в Docker-контейнере.

В новой статье от Рустама Курамшина, эксперта сообщества Spring АйО, вы узнаете:
• что именно изменилось
• как это отразится на создании Dockerfile
• и как адаптировать проекты к новым условиям.


Введение

При разработке приложений на Spring Boot создание docker-образов — частая задача, особенно в современных CI/CD-пайплайнах. Оптимизация сборки этих образов играет ключевую роль, так как позволяет сократить время последующих билдов, эффективно используя кэширование слоев docker-образа. Spring Boot помогает в этом, предоставляя возможность распаковки JAR-архива на отдельные слои: dependencies, spring-boot-loader, snapshot-dependencies и application. Такой подход позволяет перестраивать только измененные части и, например, оставлять зависимости в кэше демона docker.

С переходом от версии Spring Boot 3.2 к 3.3 изменился процесс распаковки JAR и запуска приложения внутри Docker-контейнера. В этой статье мы разберем, что именно поменялось, как это влияет на написание Dockerfile, и как адаптировать свои проекты.

Как это работало в Spring Boot 3.2

В Spring Boot 3.2 для распаковки JAR-файла использовался режим jarmode=layertools. Команда выглядела так:

java -Djarmode=layertools -jar application.jar extract

Эта команда распаковывала JAR-архив на четыре каталога:

  • dependencies — внешние зависимости;

  • spring-boot-loader — загрузчик Spring Boot;

  • snapshot-dependencies — зависимости snapshot-версий;

  • application — код приложения.

После распаковки эти каталоги копировались на отдельные слои в итоговый docker-образ, а приложение запускалось с помощью класса JarLauncher. Такой подход обеспечивал разделение слоев для эффективного кэширования, но требовал явного указания загрузчика в ENTRYPOINT в Dockerfile.

Пример Dockerfile для Spring Boot 3.2:

FROM eclipse-temurin:17-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:17-jre
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

Процесс прост: JAR-архив распаковывается в рабочей директории, каталоги копируются в итоговый образ, а запуск осуществляется через JarLauncher.

Что изменилось в Spring Boot 3.3

В Spring Boot 3.3 подход к распаковке и запуску был обновлен.
Основные изменения:

  1. Смена jarmode: Вместо jarmode=layertools теперь используется jarmode=tools.

  2. Обновленная команда распаковки: Команда extract теперь требует дополнительных опций, таких как --layers и --destination, чтобы явно указать директорию для распаковки.
    Пример:

    java -Djarmode=tools -jar my-app.jar extract --layers --destination extracted
    

    Это дает больше контроля над тем, куда распаковываются слои, в отличие от версии 3.2, где распаковка происходила в текущую директорию по умолчанию.

  3. Новый способ запуска: Вместо JarLauncher приложение теперь запускается стандартной командой java -jar application.jar. Однако важно понимать, что application.jar в этом случае — это не исходный "uber JAR", а специальный JAR-файл, содержащий только код приложения и ссылки на распакованные зависимости.

Пример Dockerfile для Spring Boot 3.3:

FROM bellsoft/liberica-openjre-debian:17-cds AS builder
WORKDIR /builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM bellsoft/liberica-openjre-debian:17-cds
WORKDIR /application
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./

ENTRYPOINT ["java", "-jar", "application.jar"]

Обратите внимание на различия:

  • Использование jarmode=tools вместо layertools.

  • Явное указание директории распаковки через --destination extracted.

  • Переход на java -jar application.jar в ENTRYPOINT.

Причины перехода на новый способ запуска распакованных JAR-файлов

Эти изменения делают процесс запуска приложения более стандартизированным. Команда java -jar — это привычный способ запуска Java-приложений, что упрощает интеграцию с другими инструментами и делает Dockerfile более интуитивным.

Еще одно новшество в Spring Boot 3.3 — поддержка Class Data Sharing (CDS). Это опционал��ная возможность, которая может ускорить запуск приложения за счет оптимизации загрузки классов, но для целей этой статьи она не является ключевой.
Новый подход позволяет писать CDS-friendly Dockerfile потому что при переходе на использование CDS вам нужно будет интегрировать в Dockerfile тренировочные запуски приложения ("training run") для получения jsa-архива с классами:

RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar

Резюмируем

Если вы обновляете проект с Spring Boot 3.2 до 3.3, вот план адаптации Dockerfile:

  1. Обновите jarmode: Замените jarmode=layertools на jarmode=tools.

  2. Добавьте опции в команду распаковки:

    Измените

    RUN java -Djarmode=tools -jar application.jar extract

    на

    RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted.

  3. Измените точку входа:

    Замените

    ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

    на

    ENTRYPOINT ["java", "-jar", "application.jar"].

  4. Обновите пути копирования: Убедитесь, что пути в COPY соответствуют новой директории распаковки (например, /builder/extracted/ вместо application/).

Заключение

Переход на Spring Boot 3.3 привносит изменения в процесс распаковки JAR и запуска приложений в Docker-контейнерах, делая его более гибким и приближенным к стандартным практикам Java. Обновление Dockerfile при миграции на новую версию
не требует значительных усилий, но важно учесть новые параметры jarmode и команду запуска.

Для ознакомления с хорошими практиками написания Dockerfiles для Spring Boot предлагаю вам ознакомиться с моим докладом на JPoint 2024 - "Правильный DevOps для Spring Boot и Java":


Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.