GraalVM 原生映像支持

1. GraalVM 原生镜像简介

GraalVM 本机映像提供了一种部署和运行 Java 应用程序的新方法。 与 Java 虚拟机相比,本机映像可以以更小的内存占用和更快的启动时间运行。spring-doc.cadn.net.cn

它们非常适合使用容器映像部署的应用程序,并且与“功能即服务”(FaaS) 平台结合使用时特别有趣。spring-doc.cadn.net.cn

与为 JVM 编写的传统应用程序不同,GraalVM 本机映像应用程序需要提前处理才能创建可执行文件。 这种提前处理涉及从其主入口点静态分析您的应用程序代码。spring-doc.cadn.net.cn

GraalVM 本机映像是一个完整的、特定于平台的可执行文件。 您无需提供 Java 虚拟机即可运行本机映像。spring-doc.cadn.net.cn

如果您只想开始并试验 GraalVM,您可以跳到“开发您的第一个 GraalVM 本机应用程序”部分,稍后返回到本部分。

1.1. 与 JVM 部署的主要区别

GraalVM 本机映像是提前生成的,这意味着本机应用程序和基于 JVM 的应用程序之间存在一些关键差异。 主要区别是:spring-doc.cadn.net.cn

除了这些差异之外,Spring 还使用了一种称为 Spring Ahead-of-Time 处理的过程,这会施加进一步的限制。 请务必至少阅读下一节的开头以了解这些内容。spring-doc.cadn.net.cn

GraalVM 参考文档的“本机映像兼容性指南”部分提供了有关 GraalVM 限制的更多详细信息。

1.2. 了解 Spring 提前处理

典型的 Spring Boot 应用程序是非常动态的,配置在运行时执行。 事实上,Spring Boot 自动配置的概念在很大程度上取决于对运行时状态的反应,以便正确配置事物。spring-doc.cadn.net.cn

尽管可以告诉 GraalVM 应用程序的这些动态方面,但这样做会抵消静态分析的大部分好处。 因此,相反,当使用 Spring Boot 创建本机映像时,假设是一个封闭的世界,并且应用程序的动态方面受到限制。spring-doc.cadn.net.cn

除了 GraalVM 本身创建的限制外,封闭世界假设还意味着以下限制:spring-doc.cadn.net.cn

当这些限制到位时,Spring 可以在构建期间执行提前处理并生成 GraalVM 可以使用的其他资产。 Spring AOT 处理的应用程序通常会生成:spring-doc.cadn.net.cn

1.2.1. 源代码生成

Spring 应用程序由 Spring Bean 组成。在内部,Spring Framework 使用两个不同的概念来管理 bean。有 bean 实例,它们是已创建的实际实例,可以注入到其他 bean 中。还有一些 bean 定义用于定义 bean 的属性以及如何创建其实例。spring-doc.cadn.net.cn

如果我们采用典型的@Configuration类:spring-doc.cadn.net.cn

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();
    }

}

Bean 定义是通过解析@Configuration类并找到@Bean方法。 在上面的示例中,我们定义了一个BeanDefinition对于名为myBean. 我们还在创建一个BeanDefinition对于MyConfiguration类本身。spring-doc.cadn.net.cn

myBean实例是必需的,Spring 知道它必须调用myBean()方法并使用结果。在 JVM 上运行时,@Configuration类解析发生在应用程序启动时,并且@Bean使用反射调用方法。spring-doc.cadn.net.cn

创建原生映像时,Spring 以不同的方式运行。而不是解析@Configuration类并在运行时生成 Bean 定义,它在构建时执行。一旦发现 Bean 定义,它们就会被处理并转换为可由 GraalVM 编译器分析的源代码。spring-doc.cadn.net.cn

Spring AOT 进程会将上面的配置类转换为如下代码:spring-doc.cadn.net.cn

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;
    }

}
生成的确切代码可能会有所不同,具体取决于 Bean 定义的性质。

您可以在上面看到,生成的代码创建了与@Configuration类,但以 GraalVM 可以理解的直接方式。spring-doc.cadn.net.cn

有一个 bean 定义myConfigurationbean,一个用于myBean. 当myBeaninstance 是必需的,则BeanInstanceSupplier被调用。此提供商将调用myBean()方法myConfiguration豆。spring-doc.cadn.net.cn

在 Spring AOT 处理期间,您的应用程序将启动到 Bean 定义可用为止。在 AOT 处理阶段不会创建 Bean 实例。

Spring AOT 将为您的所有 bean 定义生成这样的代码。当需要 bean 后处理时,它还将生成代码(例如,调用@Autowired方法)。 一ApplicationContextInitializer还将生成,Spring Boot 将使用它来初始化ApplicationContext当实际运行 AOT 处理的应用程序时。spring-doc.cadn.net.cn

尽管 AOT 生成的源代码可能很冗长,但它非常可读,并且在调试应用程序时很有帮助。生成的源文件可以在target/spring-aot/main/sources使用 Maven 和build/generated/aotSources与 Gradle 一起。

1.2.2. 提示文件生成

除了生成源文件外,Spring AOT 引擎还将生成 GraalVM 使用的提示文件。提示文件包含 JSON 数据,这些数据描述了 GraalVM 应如何通过直接检查代码来处理它无法理解的内容。spring-doc.cadn.net.cn

例如,您可能在私有方法上使用 Spring 注释。Spring 需要使用反射来调用私有方法,即使在 GraalVM 上也是如此。当出现这种情况时,Spring 可以编写反射提示,以便 GraalVM 知道即使私有方法没有被直接调用,它仍然需要在本机映像中可用。spring-doc.cadn.net.cn

提示文件在META-INF/native-imageGraalVM 会自动获取它们。spring-doc.cadn.net.cn

生成的提示文件可以在target/spring-aot/main/resources使用 Maven 和build/generated/aotResources与 Gradle 一起。

1.2.3. 代理类生成

Spring 有时需要生成代理类,以使用附加功能增强您编写的代码。为此,它使用直接生成字节码的 cglib 库。spring-doc.cadn.net.cn

当应用程序在 JVM 上运行时,代理类会在应用程序运行时动态生成。创建本机映像时,需要在构建时创建这些代理,以便 GraalVM 可以包含它们。spring-doc.cadn.net.cn

与源代码生成不同,生成的字节码在调试应用程序时并不是特别有用。 但是,如果您需要检查.class文件使用javap您可以在以下位置找到它们target/spring-aot/main/classes对于 Maven 和build/generated/aotClasses对于 Gradle。

2. 开发您的第一个 GraalVM 原生应用程序

现在我们已经很好地了解了 GraalVM 本机映像以及 Spring 提前引擎的工作原理,我们可以了解如何创建应用程序。spring-doc.cadn.net.cn

构建 Spring Boot 原生镜像应用程序有两种主要方法:spring-doc.cadn.net.cn

启动新的原生 Spring Boot 项目的最简单方法是转到 start.spring.io,添加“GraalVM Native Support”依赖项并生成项目。 包括HELP.mdfile 将提供入门提示。

2.1. 示例应用程序

我们需要一个示例应用程序,可用于创建我们的本机映像。 就我们的目的而言,“getting-started.html”部分中介绍的简单“Hello World!” Web 应用程序就足够了。spring-doc.cadn.net.cn

回顾一下,我们的主要应用程序代码如下所示:spring-doc.cadn.net.cn

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 本机映像配合使用。spring-doc.cadn.net.cn

2.2. 使用构建包构建原生镜像

Spring Boot 包括对 Maven 和 Gradle 的原生映像的构建包支持。 这意味着您只需键入一个命令,即可快速将合理的映像导入本地运行的 Docker 守护进程中。 生成的镜像不包含 JVM,而是静态编译原生镜像。 这会导致图像变小。spring-doc.cadn.net.cn

用于图像的构建器是paketobuildpacks/builder-jammy-tiny:latest. 它占用空间小,攻击面减少,但您也可以使用paketobuildpacks/builder-jammy-base:latestpaketobuildpacks/builder-jammy-full:latest如果需要,在映像中提供更多可用工具。

2.2.1. 系统要求

应安装 Docker。有关更多详细信息,请参阅获取 Docker。如果您使用的是 Linux,请将其配置为允许非 root 用户spring-doc.cadn.net.cn

您可以运行docker run hello-world(没有sudo) 以检查 Docker 守护进程是否按预期访问。 有关更多详细信息,请查看 MavenGradle Spring Boot 插件文档。
在 macOS 上,建议将分配给 Docker 的内存增加到至少8GB,并可能添加更多 CPU。 有关更多详细信息,请参阅此 Stack Overflow 答案。 在 Microsoft Windows 上,请确保启用 Docker WSL 2 后端以获得更好的性能。

2.2.2. 使用 Maven

要使用 Maven 构建原生镜像容器,您应该确保您的pom.xml文件使用spring-boot-starter-parentorg.graalvm.buildtools:native-maven-plugin. 您应该有一个<parent>部分如下所示:spring-doc.cadn.net.cn

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

您还应该在<build> <plugins>部分:spring-doc.cadn.net.cn

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

spring-boot-starter-parent声明一个native配置文件,用于配置创建本机映像所需运行的执行。 您可以使用-P标志。spring-doc.cadn.net.cn

如果您不想使用spring-boot-starter-parent您需要为process-aot目标来自 Spring Boot 插件和add-reachability-metadata目标。

要构建映像,您可以运行spring-boot:build-image目标与native配置文件活动:spring-doc.cadn.net.cn

$ mvn -Pnative spring-boot:build-image

2.2.3. 使用 Gradle

应用 GraalVM Native Image 插件时,Spring Boot Gradle 插件会自动配置 AOT 任务。 您应该检查您的 Gradle build 是否包含plugins块,包括org.graalvm.buildtools.native.spring-doc.cadn.net.cn

只要org.graalvm.buildtools.native插件,则bootBuildImage任务将生成一个本机镜像,而不是一个 JVM 镜像。 您可以使用以下命令运行任务:spring-doc.cadn.net.cn

$ gradle bootBuildImage

2.2.4. 运行示例

运行适当的构建命令后,Docker 映像应该可用。 您可以使用以下命令启动您的应用程序docker run:spring-doc.cadn.net.cn

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

您应该会看到类似于以下内容的输出:spring-doc.cadn.net.cn

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.0.13)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
启动时间因机器而异,但它应该比在 JVM 上运行的 Spring Boot 应用程序快得多。

如果您打开 Web 浏览器以localhost:8080,您应该会看到以下输出:spring-doc.cadn.net.cn

Hello World!

要正常退出应用程序,请按ctrl-c.spring-doc.cadn.net.cn

2.3. 使用本机构建工具构建本机镜像

如果要在不使用 Docker 的情况下直接生成本机可执行文件,可以使用 GraalVM 本机构建工具。 本机构建工具是 GraalVM 为 Maven 和 Gradle 提供的插件。 您可以使用它们来执行各种 GraalVM 任务,包括生成本机映像。spring-doc.cadn.net.cn

2.3.1. 前提条件

要使用本机构建工具构建本机映像,您需要在计算机上安装 GraalVM 发行版。 您可以在 Liberica Native Image Kit 页面上手动下载,也可以使用 SDKMAN!spring-doc.cadn.net.cn

Linux 和 macOS

要在 macOS 或 Linux 上安装原生镜像编译器,我们建议使用 SDKMAN!。 获取 SDKMAN!从 sdkman.io,然后使用以下命令安装 Liberica GraalVM 发行版:spring-doc.cadn.net.cn

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

通过检查java -version:spring-doc.cadn.net.cn

$ 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 上,按照以下说明在版本 22.3 中安装 GraalVMLiberica 本机映像工具包、Visual Studio 生成工具和 Windows SDK。 由于与 Windows 相关的命令行最大长度,请确保使用 x64 本机工具命令提示符而不是常规 Windows 命令行来运行 Maven 或 Gradle 插件。spring-doc.cadn.net.cn

2.3.2. 使用 Maven

buildpack 支持一样,您需要确保使用spring-boot-starter-parent为了继承nativeprofile 和org.graalvm.buildtools:native-maven-plugin插件。spring-doc.cadn.net.cn

使用nativeprofile active,您可以调用native:compile要触发的目标native-image汇编:spring-doc.cadn.net.cn

$ mvn -Pnative native:compile

本机映像可执行文件可以在target目录。spring-doc.cadn.net.cn

2.3.3. 使用 Gradle

当 Native Build Tools Gradle 插件应用于您的项目时,Spring Boot Gradle 插件将自动触发 Spring AOT 引擎。 任务依赖项是自动配置的,因此您只需运行标准nativeCompile生成原生镜像的任务:spring-doc.cadn.net.cn

$ gradle nativeCompile

本机映像可执行文件可以在build/native/nativeCompile目录。spring-doc.cadn.net.cn

2.3.4. 运行示例

此时,您的应用程序应该可以工作。您现在可以通过直接运行应用程序来启动它:spring-doc.cadn.net.cn

专家
$ target/myproject
Gradle
$ build/native/nativeCompile/myproject

您应该会看到类似于以下内容的输出:spring-doc.cadn.net.cn

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.0.13)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
启动时间因机器而异,但它应该比在 JVM 上运行的 Spring Boot 应用程序快得多。

如果您打开 Web 浏览器以localhost:8080,您应该会看到以下输出:spring-doc.cadn.net.cn

Hello World!

要正常退出应用程序,请按ctrl-c.spring-doc.cadn.net.cn

3. 测试 GraalVM 本机镜像

在编写本机映像应用程序时,我们建议您尽可能继续使用 JVM 来开发大部分单元和集成测试。 这将有助于缩短开发人员的构建时间,并允许您使用现有的 IDE 集成。 通过对 JVM 的广泛测试覆盖,您可以将本机图像测试集中在可能不同的区域。spring-doc.cadn.net.cn

对于原生映像测试,您通常希望确保以下方面有效:spring-doc.cadn.net.cn

3.1. 使用 JVM 测试提前处理

当 Spring Boot 应用程序运行时,它会尝试检测它是否作为本机映像运行。 如果它作为本机映像运行,它将使用 Spring AOT 引擎在构建时生成的代码初始化应用程序。spring-doc.cadn.net.cn

如果应用程序在常规 JVM 上运行,则将忽略任何 AOT 生成的代码。spring-doc.cadn.net.cn

由于native-image编译阶段可能需要一段时间才能完成,有时在 JVM 上运行应用程序但让它使用 AOT 生成的初始化代码很有用。 这样做有助于快速验证 AOT 生成的代码中是否没有错误,并且在应用程序最终转换为本机映像时是否缺少任何内容。spring-doc.cadn.net.cn

要在 JVM 上运行 Spring Boot 应用程序并让它使用 AOT 生成的代码,您可以将spring.aot.enabled系统属性设置为true.spring-doc.cadn.net.cn

$ java -Dspring.aot.enabled=true -jar myapplication.jar
您需要确保您正在测试的 jar 包含 AOT 生成的代码。 对于 Maven,这意味着您应该使用-Pnative激活native轮廓。 对于 Gradle,您需要确保您的构建包含org.graalvm.buildtools.native插件。

如果您的应用程序以spring.aot.enabled属性设置为true,那么您更有信心将其转换为原生图像时能够正常工作。spring-doc.cadn.net.cn

您还可以考虑针对正在运行的应用程序运行集成测试。 例如,您可以使用 SpringWebClient调用应用程序 REST 端点。 或者,您可以考虑使用像 Selenium 这样的项目来检查应用程序的 HTML 响应。spring-doc.cadn.net.cn

3.2. 使用本机构建工具进行测试

GraalVM 本机构建工具包括在本机映像中运行测试的功能。 当您想要深入测试应用程序的内部结构是否在 GraalVM 本机映像中工作时,这会很有帮助。spring-doc.cadn.net.cn

生成包含要运行的测试的本机映像可能是一项耗时的作,因此大多数开发人员可能更喜欢在本地使用 JVM。 但是,它们作为 CI 管道的一部分非常有用。 例如,您可以选择每天运行一次本机测试。spring-doc.cadn.net.cn

Spring Framework 包括对运行测试的预先支持。 所有常见的 Spring 测试功能都适用于本机映像测试。 例如,您可以继续使用@SpringBootTest注解。 您还可以使用 Spring Boot 测试切片仅测试应用程序的特定部分。spring-doc.cadn.net.cn

Spring Framework 的本机测试支持以以下方式工作:spring-doc.cadn.net.cn

3.2.1. 使用 Maven

要使用 Maven 运行本机测试,请确保您的pom.xml文件使用spring-boot-starter-parent. 您应该有一个<parent>部分如下所示:spring-doc.cadn.net.cn

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

spring-boot-starter-parent声明一个nativeTest配置文件,用于配置运行本机测试所需的执行。 您可以使用-P标志。spring-doc.cadn.net.cn

如果您不想使用spring-boot-starter-parent您需要为process-test-aot目标和test目标。

要构建映像并运行测试,请使用test目标与nativeTest配置文件活动:spring-doc.cadn.net.cn

$ mvn -PnativeTest test

3.2.2. 使用 Gradle

应用 GraalVM Native Image 插件时,Spring Boot Gradle 插件会自动配置 AOT 测试任务。 您应该检查您的 Gradle build 是否包含plugins块,包括org.graalvm.buildtools.native.spring-doc.cadn.net.cn

要使用 Gradle 运行原生测试,您可以使用nativeTest任务:spring-doc.cadn.net.cn

$ gradle nativeTest

4. 高级原生图像主题

4.1. 嵌套配置属性

Spring 提前引擎会自动为配置属性创建反射提示。 但是,非内部类的嵌套配置属性必须使用@NestedConfigurationProperty,否则它们将不会被检测到并且无法绑定。spring-doc.cadn.net.cn

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是:spring-doc.cadn.net.cn

public class Nested {

    private int number;

    // getters / setters...

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

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

}

上面的示例生成了my.properties.namemy.properties.nested.number. 如果没有@NestedConfigurationProperty注释nested字段,my.properties.nested.number属性在本机映像中不可绑定。spring-doc.cadn.net.cn

使用构造函数绑定时,您必须使用@NestedConfigurationProperty:spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

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
)
请在所有情况下使用公共 getter 和 setter,否则属性将不可绑定。

4.2. 转换 Spring Boot 可执行 jar

只要 jar 包含 AOT 生成的资产,就可以将 Spring Boot 可执行 jar 转换为本机映像。 这可能出于多种原因而有用,包括:spring-doc.cadn.net.cn

您可以使用 Cloud Native Buildpacks 或使用native-imageGraalVM 附带的工具。spring-doc.cadn.net.cn

可执行 jar 必须包含 AOT 生成的资产,例如生成的类和 JSON 提示文件。

4.2.1. 使用 Buildpack

Spring Boot 应用程序通常通过 Maven (mvn spring-boot:build-image) 或 Gradle (gradle bootBuildImage) 集成。 但是,您也可以使用pack将 AOT 处理的 Spring Boot 可执行 jar 转换为本机容器映像。spring-doc.cadn.net.cn

首先,确保 Docker 守护进程可用(有关更多详细信息,请参阅获取 Docker)。如果您使用的是 Linux,请将其配置为允许非 root 用户spring-doc.cadn.net.cn

您还需要安装pack按照 buildpacks.io 上的安装指南进行作。spring-doc.cadn.net.cn

假设 AOT 处理的 Spring Boot 可执行 jar 构建为myproject-0.0.1-SNAPSHOT.jartarget目录下,运行:spring-doc.cadn.net.cn

$ 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:spring-doc.cadn.net.cn

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

4.2.2. 使用 GraalVM 原生映像

将 AOT 处理的 Spring Boot 可执行文件 jar 转换为本机可执行文件的另一个选项是使用 GraalVMnative-image工具。 为此,您需要在计算机上安装 GraalVM 发行版。 您可以在 Liberica Native Image Kit 页面上手动下载它,也可以使用 SDKMAN!spring-doc.cadn.net.cn

假设 AOT 处理的 Spring Boot 可执行 jar 构建为myproject-0.0.1-SNAPSHOT.jartarget目录下,运行:spring-doc.cadn.net.cn

$ 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(上面的命令使用findtr来做到这一点)。

4.3. 使用跟踪代理

GraalVM 本机映像跟踪代理允许您拦截 JVM 上的反射、资源或代理使用情况,以生成相关提示。Spring 应该会自动生成其中的大部分提示,但跟踪代理可用于快速识别缺失的条目。spring-doc.cadn.net.cn

使用代理为本机图像生成提示时,有几种方法:spring-doc.cadn.net.cn

第一个选项对于识别 Spring 无法识别库或模式时缺少的提示很有趣。spring-doc.cadn.net.cn

第二个选项对于可重复的设置听起来更有吸引力,但默认情况下,生成的提示将包括测试基础设施所需的任何内容。 当应用程序实际运行时,其中一些将是不必要的。 为了解决这个问题,代理支持一个访问过滤器文件,该文件将导致某些数据从生成的输出中排除。spring-doc.cadn.net.cn

4.3.1. 直接启动应用程序

使用以下命令启动附加了本机图像跟踪代理的应用程序:spring-doc.cadn.net.cn

$ 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.spring-doc.cadn.net.cn

在应用程序关闭时,本机图像跟踪代理会将提示文件写入给定的配置输出目录。 您可以手动检查这些文件,也可以将它们用作本机映像生成过程的输入。 要将它们用作输入,请将它们复制到src/main/resources/META-INF/native-image/目录。 下次构建本机映像时,GraalVM 将考虑这些文件。spring-doc.cadn.net.cn

可以在本机图像跟踪代理上设置更高级的选项,例如按调用方类过滤记录的提示等。 如需进一步阅读,请参阅官方文档spring-doc.cadn.net.cn

4.4. 自定义提示

如果您需要为反射、资源、序列化、代理使用等提供自己的提示,您可以使用RuntimeHintsRegistrar应用程序接口。 创建一个实现RuntimeHintsRegistrar接口,然后对提供的RuntimeHints实例:spring-doc.cadn.net.cn

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带注释的应用程序类)来激活这些提示。spring-doc.cadn.net.cn

如果您有需要绑定的类(主要是在序列化或反序列化 JSON 时需要的),您可以使用@RegisterReflectionForBinding在任何豆子上。 大多数提示都是自动推断的,例如,当接受或返回来自@RestController方法。 但是,当您使用WebClientRestTemplate直接,您可能需要使用@RegisterReflectionForBinding.spring-doc.cadn.net.cn

4.4.1. 测试自定义提示

RuntimeHintsPredicatesAPI 可用于测试您的提示。该 API 提供了构建Predicate可用于测试RuntimeHints实例。spring-doc.cadn.net.cn

如果您使用的是 AssertJ,您的测试将如下所示:spring-doc.cadn.net.cn

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 本身不包含第 3 方库的提示,而是依赖于可访问性元数据项目。spring-doc.cadn.net.cn

如果您在为 Spring Boot 应用程序生成原生镜像时遇到问题,请查看 Spring Boot wiki 的 Spring Boot with GraalVM 页面。 您还可以向 GitHub 上的 spring-aot-smoke-tests 项目贡献问题,该项目用于确认常见应用程序类型是否按预期工作。spring-doc.cadn.net.cn

如果您发现库不适用于 GraalVM,请在可访问性元数据项目上提出问题。spring-doc.cadn.net.cn

5. 接下来要读什么

如果您想详细了解我们的构建插件提供的提前处理,请参阅 MavenGradle 插件文档。 要了解有关用于执行处理的 API 的更多信息,请浏览org.springframework.aot.generateorg.springframework.beans.factory.aotSpring Framework 源代码的包。spring-doc.cadn.net.cn

有关 Spring 和 GraalVM 的已知限制,请参阅 Spring Boot wikispring-doc.cadn.net.cn

下一节将继续介绍 Spring Boot CLIspring-doc.cadn.net.cn