...

суббота, 13 марта 2021 г.

Релиз Spring Native Beta

Недавно команда, занимающаяся портированием Spring для GraalVM, выпустила первый крупный релиз - Spring Native Beta. Вместе с создателями GraalVM они смогли пофиксить множество багов как в самом компиляторе так и спринге. Теперь у проекта появилась официальная поддержка, свой цикл релизов и его можно щупать :)


Самым главным препятствием при переносе кода из JVM в бинарники является проблема использования фишек, присущих только java - рефлексия, работа с classpath, динамическая загрузка классов и т.д. 

Согласно документации, ключевые различия между обычным JVM и нативной реализацией заключаются в следующем:

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

  • Неиспользуемые компоненты удаляются во время сборки.

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

  • На время сборки фиксируются все компоненты в Classpath.

  • Нет ленивой загрузки класса: при загрузке все, что поставляется в исполняемых файлах, будет загружено в память. Например, чтобы вызов Class.forName ("myClass") отработал верно, нужно иметь myClass в файле конфигурации. Если в файле конфигурации не будет найден класс, который запрашивается для динамической загрузки класса, будет выбрано исключение ClassNotFoundException

  • Часть кода будет запущена во время сборки, чтобы правильно связать компоненты. Например, тесты.

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

В ходе исследований был создан новый компонент Spring AOT, который отвечает за все необходимые преобразования вашего кода в удобоваримый для Graal VM формат.

Spring AOT анализирует код и на основе него создает файлы конфигурации такие как native-image.properties, reflection-config.json, proxy-config.json или resource-config.json.

Так как Graal VM поддерживает первоначальную настройку через статические файлы, эти файлы помещаются при сборке в каталог META-INF/native-image

Для каждого сборщика выпущен свой плагин, который активирует работу Spring AOT. Для maven это spring-aot-maven-plugin, соответственно для gradle - spring-aot-gradle-plugin.Для того, чтобы добавить gradle плагин в свой проект нужна всего одна строка:

plugins {id 'org.springframework.experimental.aot' version '0.9.0'}

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

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

Например, для случаев реализации компонентов с помощью WebClient можно использовать аннотацию из пакета org.springframework.nativex.hint, чтобы указать какой тип мы будем обрабатывать:

@TypeHint(types = Data.class, typeNames = "com.example.webclient.Data$SuperHero")
@SpringBootApplication
public class WebClientApplication {
        // ...
}

Здесь мы указываем, что будем сериализовать класс Data, в котором есть подкласс SuperHero. Во время сборки для нас заранее создадут клиент, который сможет работать с этим типом данных.

Так как graavlvm не поддерживает работу с динамическими прокси, то для поддержки работы с java.lang.reflect.Proxy создана аннотация @ProxyHint.

Применять ее можно, например, так:

@ProxyHint(types = {
     org.hibernate.Session.class,
     org.springframework.orm.jpa.EntityManagerProxy.class
})

Если необходимо подтянуть какие-либо ресурсы в образ, то необходимо воспользоваться аннотацией @ResourceHint.Например, таким образом:

@ResourceHint(patterns = "com/mysql/cj/TlsSettings.properties")

Чтобы указать какие классы / пакеты должны быть инициализированы явно во время сборки или выполнения, нужно воспользоваться аннотацией @InitializationHint:

@InitializationHint(types = org.h2.util.Bits.class,
                                                                    initTime = InitializationTime.BUILD)

Для того, чтобы компактно собрать все эти аннотации воедино создана аннотация @NativeHint:

@Repeatable(NativeHints.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeHint

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

@NativeHint(
    trigger = Driver.class,
    options = "--enable-all-security-services",
    types = @TypeHint(types = {
       FailoverConnectionUrl.class,
       FailoverDnsSrvConnectionUrl.class,
       // ...
    }), resources = {
        @ResourceHint(patterns = "com/mysql/cj/TlsSettings.properties"),
        @ResourceHint(patterns = "com.mysql.cj.LocalizedErrorMessages",
                      isBundle = true)
})

В качестве тригера, мы выбираем тот класс, присутствие которого в classpath должно вызвать построение конфигурации.

Все активные аннотации учитываются во время компиляции и преобразуются в конфигурацию Graal VM плагином Spring AOT. 

Spring Native уже включена в релизный цикл, забрать шаблон можно прямо со start.spring.io. Так как поддержка JPA и прочих spring компонентов уже реализована, то собрать простое CRUD приложение можно сразу. Если необходимо указать дополнительные параметры Graal VM при сборке, их можно добавить с помощью переменной среды BP_NATIVE_IMAGE_BUILD_ARGUMENTS в плагине Spring AOT, если сборка идет через Buildpacks, или с помощью элемента конфигурации “<buildArgs>” в pom.xml, если вы собираете через плагин native-image-maven-plugin.

Собственно, выполняем команды mvn spring-boot: build-image или gradle bootBuildImage - и начнется сборка образа. Стоит отметить, что сборщику нужно более 7 Гб памяти, для того сборка завершилась успешно. На моей машине сборка, вместе с загрузкой образов заняла не более 5 минут. При этом образ получился очень компактным, всего 60 Мб. Стартовало приложение за 0.022 секунды! Это невероятный результат. Учитывая, что все большее количество компаний переходит на K8s и старт приложения, так же как и используемые ресурсы очень важны в современном мире, то данная технология позволяет Spring сделать фреймворком номер один для всех типов микросервисов, даже для реализаций FaaS, где очень важна скорость холодного старта.

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий