Spec-Zone.ru › Spring Boot

Поддержка GraalVM Native Image

Образы GraalVM Native Image — это автономные исполняемые файлы, которые могут быть сгенерированы путём предварительной компиляции Java-приложений. Образы Native Image, как правило, занимают меньше памяти и запускаются быстрее, чем их аналоги, основанные на JVM.

1. Введение в GraalVM Native Image

GraalVM Native Image предоставляют новый способ развертывания и запуска Java-приложений. По сравнению с Java Virtual Machine, образы native image могут работать с меньшим потреблением памяти и значительно более быстрым временем запуска.

Они идеально подходят для приложений, развертываемых с помощью контейнерных образов, и особенно интересны в сочетании с платформами «Функция как услуга» (FaaS).

В отличие от традиционных приложений, написанных для JVM, приложения GraalVM Native Image требуют предварительной обработки, чтобы создать исполняемый файл. Эта предварительная обработка включает в себя статический анализ кода вашего приложения начиная с точки входа.

Образ GraalVM Native Image — это завершённый, платформоспецифичный исполняемый файл. Вам не нужно поставлять Java Virtual Machine, чтобы запустить native image.

Если вы хотите просто начать работу и поэкспериментировать с GraalVM, можете перейти к разделу «Разработка вашего первого GraalVM Native Application» и вернуться к этому разделу позже.

1.1. Ключевые различия с развертыванием на JVM

Тот факт, что GraalVM Native Image создаются заранее, означает, что существуют ключевые различия между приложениями, основанными на native image и на JVM. Основные различия:

  • Статический анализ вашего приложения выполняется во время сборки с точки входа main.

  • Код, которого нельзя достичь при создании native image, будет удалён и не войдёт в состав исполняемого файла.

  • GraalVM не имеет прямого доступа к динамическим элементам вашего кода и должен быть проинформирован о рефлексии, ресурсах, сериализации и динамических прокси.

  • Путь к файлам приложения фиксируется на этапе сборки и не может быть изменён.

  • Отсутствует отложенная загрузка классов; всё, что входит в состав исполняемого файла, будет загружено в память при запуске.

  • Есть некоторые ограничения в отношении некоторых аспектов Java-приложений, которые не полностью поддерживаются.

Раздел Руководства по совместимости Native Image в справочной документации GraalVM содержит более подробную информацию об ограничениях GraalVM.

1.2. Понимание предварительной обработки Spring

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

Хотя было бы возможно сообщить GraalVM об этих динамических аспектах приложения, это свело бы на нет большую часть преимуществ статического анализа. Поэтому при использовании Spring Boot для создания native image предполагается замкнутая система, и динамические аспекты приложения ограничены.

Предположение замкнутой системы подразумевает следующие ограничения:

  • Путь к файлам приложения фиксирован и полностью определён на этапе сборки.

  • Определённые в вашем приложении бины не могут изменяться во время выполнения, что означает:

    • Аннотация Spring @Profile и конфигурация, специфичная для профилей имеет ограничения.

    • Свойства, которые изменяются при создании бина, не поддерживаются (например, свойства @ConditionalOnProperty и .enable).

Когда эти ограничения соблюдаются, Spring может выполнить предварительную обработку во время сборки и сгенерировать дополнительные ресурсы, которые может использовать GraalVM. Обработанное приложение Spring AOT, как правило, генерирует:

  • Исходный код Java

  • Байт-код (для динамических прокси и т. д.)

  • Файлы подсказок GraalVM в формате JSON:

    • Подсказки ресурсов (resource-config.json)

    • Подсказки рефлексии (reflect-config.json)

    • Подсказки сериализации (serialization-config.json)

    • Подсказки Java Proxy (proxy-config.json)

    • Подсказки JNI (jni-config.json)

1.2.1. Генерация исходного кода

Приложения Spring состоят из Spring Bean. Внутренне Spring Framework использует два разных понятия для управления бин. Это экземпляры бинов, которые являются фактическими экземплярами, которые были созданы и могут быть внедрены в другие бины. Также есть определения бинов, которые используются для определения атрибутов бина и способа создания его экземпляра.

Если мы возьмём типичный @Configuration класс:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

}

Определение бина создаётся путём анализа класса @Configuration и поиска методов @Bean. В приведённом примере мы определяем BeanDefinition для одиночного бина с именем myBean. Мы также создаём BeanDefinition для самого класса MyConfiguration.

Когда требуется экземпляр myBean, Spring знает, что должен вызвать метод myBean() и использовать результат. При работе в JVM анализ класса @Configuration происходит при запуске приложения, и методы @Bean вызываются с помощью рефлексии.

При создании native image Spring действует по-другому. Вместо анализа классов @Configuration и генерации определений бинов во время выполнения, это делается на этапе сборки. После того как определения бинов будут найдены, они будут обработаны и преобразованы в исходный код, который может быть проанализирован компилятором GraalVM.

Процесс Spring AOT преобразует вышеприведённый конфигурационный класс в код, подобный этому:

import org.springframework.beans.factory.aot.BeanInstanceSupplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;

/**
 * Bean definitions for {@link MyConfiguration}.
 */
public class MyConfiguration__BeanDefinitions {

    /**
     * Get the bean definition for 'myConfiguration'.
     */
    public static BeanDefinition getMyConfigurationBeanDefinition() {
        Class<?> beanType = MyConfiguration.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(MyConfiguration::new);
        return beanDefinition;
    }

    /**
     * Get the bean instance supplier for 'myBean'.
     */
    private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
        return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean")
            .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
    }

    /**
     * Get the bean definition for 'myBean'.
     */
    public static BeanDefinition getMyBeanBeanDefinition() {
        Class<?> beanType = MyBean.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
        return beanDefinition;
    }

}
Точный сгенерированный код может отличаться в зависимости от типа ваших определений бинов.

Вы можете видеть выше, что сгенерированный код создаёт эквивалентные определения бинов для класса @Configuration, но прямым способом, понятным для GraalVM.

Есть определение бина для бина myConfiguration и для myBean. Когда требуется экземпляр myBean, вызывается BeanInstanceSupplier. Этот поставщик вызовет метод myBean() в бине myConfiguration.

Во время обработки Spring AOT ваше приложение запускается до момента, когда доступны определения бинов. Экземпляры бинов не создаются во время фазы обработки AOT.

Spring AOT сгенерирует такой код для всех определений бинов. Он также сгенерирует код при необходимости пост-обработки бинов (например, для вызова методов @Autowired). Также будет сгенерирован ApplicationContextInitializer, который будет использован Spring Boot для инициализации ApplicationContext при фактическом запуске обработанного приложения AOT.

Хотя сгенерированный код AOT может быть объёмным, он достаточно удобочитаем и может быть полезен при отладке приложения. Сгенерированные исходные файлы можно найти в target/spring-aot/main/sources при использовании Maven и build/generated/aotSources при использовании Gradle.

1.2.2. Генерация файлов подсказок

Помимо генерации исходных файлов, движок Spring AOT также сгенерирует файлы подсказок, которые используются GraalVM. Файлы подсказок содержат данные JSON, описывающие, как GraalVM должен обрабатывать ситуации, которые он не может понять, непосредственно анализируя код.

Например, вы можете использовать аннотацию Spring для приватного метода. Spring будет использовать рефлексию для вызова приватных методов, даже в GraalVM. В таких ситуациях Spring может записать подсказку рефлексии, чтобы GraalVM знал, что, хотя приватный метод не вызывается напрямую, он всё же должен быть доступен в native image.

Файлы подсказок генерируются в META-INF/native-image, где они автоматически подхватываются GraalVM.

Сгенерированные файлы подсказок можно найти в target/spring-aot/main/resources при использовании Maven и build/generated/aotResources при использовании Gradle.

1.2.3. Генерация классов прокси

Spring иногда нуждается в генерации классов прокси для добавления дополнительных функций к вашему коду. Для этого используется библиотека cglib, которая непосредственно генерирует байт-код.

Когда приложение работает на JVM, классы прокси генерируются динамически по мере работы приложения. При создании native image эти прокси необходимо создавать на этапе сборки, чтобы они могли быть включены в GraalVM.

В отличие от генерации исходного кода, сгенерированный байт-код не особенно полезен при отладке приложения. Однако, если вам нужно проверить содержимое файлов .class с помощью инструмента, такого как javap, вы можете найти их в target/spring-aot/main/classes при использовании Maven и build/generated/aotClasses при использовании Gradle.

2. Разработка вашего первого приложения GraalVM Native

Теперь, когда у нас есть хорошее представление о собственных образах GraalVM и о том, как работает движок Spring ahead-of-time, мы можем посмотреть, как создать приложение.

Существует два основных способа создания приложения собственного образа Spring Boot:

  • Использование поддержки Spring Boot для Cloud Native Buildpacks для генерации легковесного контейнера, содержащего собственный исполняемый файл.

  • Использование инструментов сборки GraalVM Native для генерации собственного исполняемого файла.

Проще всего начать новый проект Spring Boot native — перейти на start.spring.io, добавить зависимость «GraalVM Native Support» и сгенерировать проект. Включенный файл HELP.md предоставит подсказки для начала работы.

2.1. Пример приложения

Нам нужно приложение-пример, которое мы можем использовать для создания нашего собственного образа. Для наших целей подойдет простое веб-приложение «Hello World!», описанное в разделе «getting-started.html».

Вкратце, наш основной код приложения выглядит так:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class MyApplication {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

Это приложение использует Spring MVC и встроенный Tomcat, оба из которых были протестированы и проверены на работу с собственными образами GraalVM.

2.2. Сборка собственного образа с помощью Buildpacks

Spring Boot включает поддержку buildpack для собственных образов непосредственно для Maven и Gradle. Это означает, что вы можете просто ввести одну команду и быстро получить осмысленный образ в ваш локально запущенный демон Docker. Полученный образ не содержит JVM, вместо этого собственный образ компилируется статически. Это приводит к уменьшению размера образов.

Используемый для образов билдер — paketobuildpacks/builder:tiny. Он имеет небольшой объем и уменьшенную поверхность атаки, но вы также можете использовать paketobuildpacks/builder-jammy-base или paketobuildpacks/builder-jammy-full, чтобы иметь больше инструментов, доступных в образе, если необходимо.

2.2.1. Системные требования

Должен быть установлен Docker. Более подробную информацию см. в разделе Получение Docker. Настройте его для разрешения использования пользователем без прав root, если вы используете Linux.

Вы можете запустить docker run hello-world (без sudo) чтобы проверить, что демон Docker доступен как ожидалось. Дополнительные сведения см. в документации по плагину Spring Boot для Maven или Gradle.
В macOS рекомендуется увеличить объем памяти, выделенной для Docker, как минимум до 8GB, и, возможно, добавить больше процессоров. Дополнительные сведения см. в этом ответе на Stack Overflow. В Microsoft Windows обязательно включите бэкэнд Docker WSL 2 для повышения производительности.

2.2.2. Использование Maven

Чтобы создать контейнер собственного образа с помощью Maven, убедитесь, что ваш файл pom.xml использует spring-boot-starter-parent и org.graalvm.buildtools:native-maven-plugin. У вас должен быть раздел <parent>, который выглядит так:

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

Кроме того, у вас должно быть это в разделе <build> <plugins>;

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

spring-boot-starter-parent объявляет профиль native, который настраивает выполнения, которые необходимо выполнить для создания собственного образа. Вы можете активировать профили с помощью флага -P в командной строке.

Если вы не хотите использовать spring-boot-starter-parent, вам потребуется настроить выполнения для цели process-aot из плагина Spring Boot и цели add-reachability-metadata из плагина Native Build Tools.

Чтобы создать образ, вы можете запустить цель spring-boot:build-image с активным профилем native;

$ mvn -Pnative spring-boot:build-image

2.2.3. Использование Gradle

Плагин Spring Boot Gradle автоматически настраивает задачи AOT, когда применяется плагин GraalVM Native Image. Вам следует проверить, что ваша сборка Gradle содержит блок plugins, который включает org.graalvm.buildtools.native.

Пока применен плагин org.graalvm.buildtools.native, задача bootBuildImage будет генерировать собственный образ, а не образ JVM. Вы можете запустить задачу, используя:

$ gradle bootBuildImage

2.2.4. Запуск примера

После выполнения соответствующей команды сборки образ Docker должен быть доступен. Вы можете запустить свое приложение, используя docker run;

$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT

Вы должны увидеть вывод, похожий на следующий:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.1.3)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
Время запуска отличается от машины к машине, но оно должно быть намного быстрее, чем приложение Spring Boot, работающее на JVM.

Если вы откроете веб-браузер по адресу localhost:8080, вы должны увидеть следующий вывод:

Hello World!

Для корректного завершения работы приложения нажмите ctrl-c.

2.3. Создание нативного изображения с помощью инструментов нативного компилятора

Если вы хотите сгенерировать нативный исполняемый файл напрямую, без использования Docker, вы можете использовать инструменты GraalVM Native Build Tools. Инструменты нативного компилятора — это плагины, поставляемые GraalVM как для Maven, так и для Gradle. Вы можете использовать их для выполнения различных задач GraalVM, включая создание нативного изображения.

2.3.1. Предварительные условия

Для создания нативного изображения с помощью инструментов нативного компилятора вам потребуется дистрибутив GraalVM на вашей машине. Вы можете скачать его вручную на странице Liberica Native Image Kit, или воспользоваться менеджером загрузок, например, SDKMAN!.

Linux и macOS

Для установки компилятора нативного кода на macOS или Linux рекомендуется использовать SDKMAN!. Скачайте SDKMAN! с sdkman.io и установите дистрибутив Liberica GraalVM, используя следующие команды:

$ sdk install java 22.3.r17-nik
$ sdk use java 22.3.r17-nik

Проверьте, что правильная версия настроена, проверив вывод java -version.

$ java -version
openjdk version "17.0.5" 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode)
Windows

В Windows следуйте этим инструкциям для установки GraalVM или Liberica Native Image Kit версии 22.3, Visual Studio Build Tools и Windows SDK. Из-за ограничений максимальной длины командной строки в Windows, убедитесь, что вы используете x64 Native Tools Command Prompt вместо обычной командной строки Windows для запуска плагинов Maven или Gradle.

2.3.2. Использование Maven

Как и в случае с поддержкой buildpack, необходимо убедиться, что вы используете spring-boot-starter-parent, чтобы унаследовать профиль native, и что используется плагин org.graalvm.buildtools:native-maven-plugin.

При активном профиле native, вы можете вызвать цель native:compile, чтобы запустить компиляцию native-image.

$ mvn -Pnative native:compile

Исполняемый файл нативного изображения можно найти в каталоге target.

2.3.3. Использование Gradle

Когда плагин Gradle Native Build Tools применён к вашему проекту, плагин Spring Boot Gradle автоматически запустит движок Spring AOT. Зависимости задач настраиваются автоматически, поэтому вы можете просто запустить стандартную задачу nativeCompile, чтобы сгенерировать нативное изображение:

$ gradle nativeCompile

Исполняемый файл нативного изображения можно найти в каталоге build/native/nativeCompile.

2.3.4. Запуск примера

На этом этапе ваше приложение должно работать. Теперь вы можете запустить приложение напрямую:

Maven
$ target/myproject
Gradle
$ build/native/nativeCompile/myproject

Вы должны увидеть вывод, подобный следующему:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.1.3)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
Время запуска зависит от машины, но оно должно быть намного быстрее, чем запуск приложения Spring Boot в JVM.

Если открыть веб-браузер по адресу localhost:8080, вы должны увидеть следующий вывод:

Hello World!

Для корректного завершения работы приложения нажмите ctrl-c.

3. Тестирование GraalVM Native Images

При написании приложений с использованием native image, рекомендуется продолжать использовать JVM для разработки большинства юнит- и интеграционных тестов. Это поможет сократить время сборки для разработчиков и позволит использовать существующие интеграции IDE. После того как вы получите обширное покрытие тестами на JVM, вы сможете сфокусироваться на тестировании native image в областях, которые, вероятно, будут отличаться.

При тестировании native image, как правило, необходимо убедиться, что работают следующие аспекты:

  • Двигатель Spring AOT способен обработать ваше приложение, и оно будет работать в режиме обработки AOT.

  • GraalVM имеет достаточно подсказок, чтобы обеспечить создание корректного native image.

3.1. Тестирование предварительной обработки с помощью JVM

Когда приложение Spring Boot запускается, оно пытается определить, запускается ли оно как native image. Если это native image, оно будет инициализировано с помощью кода, сгенерированного во время компиляции движком Spring AOT.

Если приложение запускается на обычной JVM, то любой сгенерированный код AOT игнорируется.

Поскольку фаза компиляции native-image может занимать некоторое время, иногда полезно запустить ваше приложение на JVM, но использовать сгенерированный код инициализации AOT. Это поможет быстро проверить, что в сгенерированном коде AOT нет ошибок и ничего не пропущено, когда ваше приложение в конечном итоге будет преобразовано в native image.

Чтобы запустить приложение Spring Boot на JVM и использовать сгенерированный код AOT, вы можете установить системную переменную spring.aot.enabled в значение true.

Например:

$ java -Dspring.aot.enabled=true -jar myapplication.jar
Вам нужно убедиться, что jar-файл, который вы тестируете, включает сгенерированный код AOT. Для Maven это означает, что вы должны скомпилировать с -Pnative для активации профиля native. Для Gradle вам нужно убедиться, что ваша сборка включает плагин org.graalvm.buildtools.native.

Если ваше приложение запускается с установленной переменной spring.aot.enabled в значение true, то у вас больше уверенности, что оно будет работать при преобразовании в native image.

Вы также можете рассмотреть возможность выполнения интеграционных тестов против работающего приложения. Например, вы можете использовать Spring WebClient для вызова REST-точек вашего приложения. Или вы можете рассмотреть использование проекта, такого как Selenium, для проверки HTML-ответов вашего приложения.

3.2. Тестирование с помощью инструментов native сборки

Инструменты GraalVM Native Build Tools включают возможность запуска тестов внутри native image. Это может быть полезно, когда вы хотите глубоко проверить работу внутренних компонентов вашего приложения в GraalVM native image.

Генерация native image, содержащей тесты для запуска, может быть длительной операцией, поэтому большинство разработчиков, вероятно, предпочтут использовать JVM локально. Тем не менее, они могут быть очень полезны в рамках CI-пайплайна. Например, вы можете выбрать запуск native тестов один раз в день.

Spring Framework включает поддержку предварительной обработки для запуска тестов. Все стандартные функции тестирования Spring работают с native image тестами. Например, вы можете продолжить использовать аннотацию @SpringBootTest. Вы также можете использовать Spring Boot слайсы тестов для тестирования только определённых частей вашего приложения.

Поддержка тестирования native в Spring Framework работает следующим образом:

  • Тесты анализируются для выявления всех экземпляров ApplicationContext, которые потребуются.

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

  • Создаётся native image, при этом сгенерированные ресурсы обрабатываются GraalVM.

  • Native image также включает JUnit TestEngine с перечнем обнаруженных тестов.

  • Native image запускается, запускается движок, который выполнит каждый тест и сообщит результаты.

3.2.1. Использование Maven

Чтобы запустить native тесты с помощью Maven, убедитесь, что ваш файл pom.xml использует spring-boot-starter-parent. У вас должен быть раздел <parent>, который выглядит так:

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

spring-boot-starter-parent объявляет профиль nativeTest, который настраивает необходимые выполнения для запуска native тестов. Вы можете активировать профили с помощью флага -P в командной строке.

Если вы не хотите использовать spring-boot-starter-parent, вам потребуется настроить выполнение для цели process-test-aot из плагина Spring Boot и цели test из плагина Native Build Tools.

Чтобы сгенерировать image и запустить тесты, используйте цель test с активным профилем nativeTest.

$ mvn -PnativeTest test

3.2.2. Использование Gradle

Плагин Spring Boot Gradle автоматически настраивает задачи AOT-тестов, когда применяется плагин GraalVM Native Image. Вы должны проверить, что ваш Gradle билдер содержит блок plugins, который включает org.graalvm.buildtools.native.

Чтобы запустить native тесты с помощью Gradle, вы можете использовать задачу nativeTest.

$ gradle nativeTest

4. Дополнительные темы по нативным изображениям

4.1. Вложенные свойства конфигурации

Подсказки рефлексии автоматически создаются для свойств конфигурации движком Spring ahead-of-time. Однако вложенные свойства конфигурации, которые не являются внутренними классами, должны быть аннотированы с помощью @NestedConfigurationProperty, иначе они не будут обнаружены и не будут привязаны.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties(prefix = "my.properties")
public class MyProperties {

    private String name;

    @NestedConfigurationProperty
    private final Nested nested = new Nested();

    // getters / setters...

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Nested getNested() {
        return this.nested;
    }

}

где Nested это:

public class Nested {

    private int number;

    // getters / setters...

    public int getNumber() {
        return this.number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

}

Приведенный выше пример создает свойства конфигурации для my.properties.name и my.properties.nested.number. Без аннотации @NestedConfigurationProperty на поле nested, свойство my.properties.nested.number не будет привязано в нативном изображении.

При использовании привязки к конструктору, вам необходимо аннотировать поле с помощью @NestedConfigurationProperty:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties(prefix = "my.properties")
public class MyPropertiesCtor {

    private final String name;

    @NestedConfigurationProperty
    private final Nested nested;

    public MyPropertiesCtor(String name, Nested nested) {
        this.name = name;
        this.nested = nested;
    }

    // getters / setters...

    public String getName() {
        return this.name;
    }

    public Nested getNested() {
        return this.nested;
    }

}

При использовании записей, вы должны аннотировать параметр с помощью @NestedConfigurationProperty:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties(prefix = "my.properties")
public record MyPropertiesRecord(String name, @NestedConfigurationProperty Nested nested) {

}

При использовании Kotlin, вам необходимо аннотировать параметр класса данных с помощью @NestedConfigurationProperty:

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.NestedConfigurationProperty

@ConfigurationProperties(prefix = "my.properties")
data class MyPropertiesKotlin(
    val name: String,
    @NestedConfigurationProperty val nested: Nested
)
В всех случаях используйте публичные геттеры и сеттеры, иначе свойства не будут привязаны.

4.2. Преобразование исполняемого JAR-файла Spring Boot

Возможно преобразовать исполняемый JAR-файл Spring Boot в нативное изображение, если JAR содержит сгенерированные AOT-активы. Это может быть полезно по ряду причин, включая:

  • Вы можете сохранить свой обычный JVM-пайплайн и преобразовать JVM-приложение в нативное изображение на вашей платформе CI/CD.

  • Так как native-image не поддерживает кросс-компиляцию, вы можете сохранить независимый от ОС артефакт развертывания, который вы позже конвертируете в различные архитектуры ОС.

Вы можете преобразовать исполняемый JAR-файл Spring Boot в нативное изображение, используя Cloud Native Buildpacks или инструмент native-image, поставляемый с GraalVM.

Ваш исполняемый JAR-файл должен включать сгенерированные AOT-активы, такие как сгенерированные классы и JSON-файлы подсказок.

4.2.1. Использование Buildpacks

Приложения Spring Boot обычно используют Cloud Native Buildpacks через интеграции Maven (mvn spring-boot:build-image) или Gradle (gradle bootBuildImage). Однако вы также можете использовать pack, чтобы преобразовать обработанный AOT исполняемый JAR-файл Spring Boot в нативное контейнерное изображение.

Сначала убедитесь, что у вас доступен Docker-демон (подробнее см. Получение Docker). На Linux настройте его для работы с пользователем, отличным от root .

Вам также необходимо установить pack, следуя руководству по установке на buildpacks.io.

Предполагая, что обработанный AOT исполняемый JAR-файл Spring Boot, созданный как myproject-0.0.1-SNAPSHOT.jar, находится в каталоге target, выполните:

$ pack build --builder paketobuildpacks/builder-jammy-tiny \
    --path target/myproject-0.0.1-SNAPSHOT.jar \
    --env 'BP_NATIVE_IMAGE=true' \
    my-application:0.0.1-SNAPSHOT
Вам не нужна локальная установка GraalVM для создания изображения таким образом.

После завершения pack, вы можете запустить приложение, используя docker run:

$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT

4.2.2. Использование GraalVM native-image

Другой вариант преобразования обработанного AOT исполняемого JAR-файла Spring Boot в нативный исполняемый файл — использовать инструмент GraalVM native-image. Для этого вам потребуется дистрибутив GraalVM на вашей машине. Вы можете скачать его вручную на странице Liberica Native Image Kit или использовать менеджер загрузок, например SDKMAN!.

Предполагая, что обработанный AOT исполняемый JAR-файл Spring Boot, созданный как myproject-0.0.1-SNAPSHOT.jar, находится в каталоге target, выполните:

$ rm -rf target/native
$ mkdir -p target/native
$ cd target/native
$ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar
$ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'`
$ mv myproject ../
Эти команды работают на машинах Linux или macOS, но вам нужно будет адаптировать их для Windows.
@META-INF/native-image/argfile может отсутствовать в вашем JAR. Он включается только при необходимости переопределения метаданных достижимости.
Флаг native-image -cp инструмента не поддерживает подстановочные знаки. Необходимо убедиться, что все JAR-файлы указаны (в приведенной выше команде используется find и tr для этого).

4.3. Использование агента отслеживания

Агент отслеживания GraalVM native image позволяет перехватывать рефлексию, ресурсы или использование прокси в JVM для генерации соответствующих подсказок. Spring должен автоматически генерировать большинство из этих подсказок, но агент отслеживания может использоваться для быстрого выявления отсутствующих записей.

При использовании агента для генерации подсказок для нативного изображения существуют несколько подходов:

  • Запустить приложение напрямую и задействовать его.

  • Запустить тесты приложения для его задействования.

Первый вариант интересен для выявления отсутствующих подсказок, когда библиотека или паттерн не распознается Spring.

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

4.3.1. Запуск приложения напрямую

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

$ java -Dspring.aot.enabled=true \
    -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
    -jar target/myproject-0.0.1-SNAPSHOT.jar

Теперь вы можете задействовать необходимые пути кода и затем остановить приложение с помощью ctrl-c.

При завершении работы приложения агент отслеживания нативного изображения запишет файлы подсказок в указанный каталог конфигурации вывода. Вы можете либо вручную проверить эти файлы, либо использовать их в качестве входных данных для процесса создания нативного изображения. Для использования в качестве входных данных скопируйте их в каталог src/main/resources/META-INF/native-image/ . При следующем построении нативного изображения GraalVM учтет эти файлы.

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

4.4. Пользовательские подсказки

Если вам необходимо предоставить собственные подсказки для рефлексии, ресурсов, сериализации, использования прокси и т. д., вы можете использовать API RuntimeHintsRegistrar. Создайте класс, реализующий интерфейс RuntimeHintsRegistrar, а затем выполните соответствующие вызовы предоставленного экземпляра RuntimeHints:

import java.lang.reflect.Method;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Register method for reflection
        Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
        hints.reflection().registerMethod(method, ExecutableMode.INVOKE);

        // Register resources
        hints.resources().registerPattern("my-resource.txt");

        // Register serialization
        hints.serialization().registerType(MySerializableClass.class);

        // Register proxy
        hints.proxies().registerJdkProxy(MyInterface.class);
    }

}

Затем вы можете использовать @ImportRuntimeHints для любого класса @Configuration (например, вашего класса приложения, аннотированного @SpringBootApplication), чтобы активировать эти подсказки.

Если у вас есть классы, которым требуется привязка (в основном необходима при сериализации или десериализации JSON), вы можете использовать @RegisterReflectionForBinding для любого бинa. Большинство подсказок автоматически вычисляются, например, при приеме или возврате данных из метода @RestController. Но когда вы работаете с WebClient или RestTemplate напрямую, вам может потребоваться использовать @RegisterReflectionForBinding.

4.4.1. Тестирование пользовательских подсказок

API RuntimeHintsPredicates можно использовать для тестирования ваших подсказок. API предоставляет методы для построения Predicate, который может использоваться для тестирования экземпляра RuntimeHints.

Если вы используете AssertJ, ваш тест будет выглядеть так:

import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.docs.nativeimage.advanced.customhints.MyRuntimeHints;

import static org.assertj.core.api.Assertions.assertThat;

class MyRuntimeHintsTests {

    @Test
    void shouldRegisterHints() {
        RuntimeHints hints = new RuntimeHints();
        new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());
        assertThat(RuntimeHintsPredicates.resource().forResource("my-resource.txt")).accepts(hints);
    }

}

4.5. Известные ограничения

Нативные образы GraalVM — это развивающаяся технология, и не все библиотеки поддерживают ее. Сообщество GraalVM помогает, предоставляя метаданные достижимости для проектов, которые еще не предоставляют собственных. Сам Spring не содержит подсказок для библиотек сторонних производителей и вместо этого полагается на проект метаданных достижимости.

Если у вас возникли проблемы при генерации нативных образов для приложений Spring Boot, пожалуйста, проверьте страницу Spring Boot с GraalVM в вики-справочнике Spring Boot. Вы также можете добавить вопросы в проект spring-aot-smoke-tests на GitHub, который используется для подтверждения того, что общие типы приложений работают как ожидается.

Если вы найдете библиотеку, которая не работает с GraalVM, пожалуйста, создайте вопрос на проекте метаданных достижимости.

5. Что читать дальше

Если вы хотите узнать больше о предварительной обработке, предоставляемой нашими плагинами сборки, см. документацию плагина Maven и Gradle. Чтобы узнать больше об API, используемых для выполнения обработки, просмотрите пакеты org.springframework.aot.generate и org.springframework.beans.factory.aot исходного кода Spring Framework.

Для ознакомления с известными ограничениями Spring и GraalVM, пожалуйста, обратитесь к вики-странице Spring Boot.

Следующий раздел посвящен интерфейсной оболочке Spring Boot.

Copyright © 2012-2023 VMware, Inc.
Licensed under the Apache License, Version 2.0.
https://docs.spring.io/spring-boot/docs/3.1.3/reference/html/native-image.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API