GraalVM Native Image 支持

1. GraalVM 原生镜像简介

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

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

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

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

如果您只想开始并试用 GraalVM,可以跳到“开发您的第一个 GraalVM 原生应用”部分,稍后再返回此部分。

1.1. 与 JVM 部署的主要区别

GraalVM Native Images 是提前生成的,这意味着原生应用和基于 JVM 的应用之间存在一些关键差异。 主要区别在于:spring-doc.cn

  • 应用程序的静态分析是在构建时从入口点执行的。mainspring-doc.cn

  • 创建本机映像时无法访问的代码将被删除,并且不会成为可执行文件的一部分。spring-doc.cn

  • GraalVM 无法直接了解代码的动态元素,必须了解反射、资源、序列化和动态代理。spring-doc.cn

  • 应用程序 Classpath 在构建时是固定的,无法更改。spring-doc.cn

  • 没有延迟类加载,可执行文件中附带的所有内容都将在启动时加载到内存中。spring-doc.cn

  • Java 应用程序的某些方面存在一些不完全支持的限制。spring-doc.cn

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

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

1.2. 了解 Spring 预先处理

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

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

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

  • 应用程序中定义的 bean 在运行时不能更改,这意味着:spring-doc.cn

    • Spring 注解和特定于配置文件的配置有限制@Profilespring-doc.cn

    • 不支持在创建 Bean 时更改的属性(例如,和 properties)。@ConditionalOnProperty.enablespring-doc.cn

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

如果生成的提示不够,您还可以提供自己的提示。spring-doc.cn

1.2.1. 源码生成

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

如果我们选取一个典型的类:@Configurationspring-doc.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 定义是通过解析类并查找方法创建的。 在上面的示例中,我们为名为 . 我们还为类本身创建了一个 API。@Configuration@BeanBeanDefinitionmyBeanBeanDefinitionMyConfigurationspring-doc.cn

当需要实例时, Spring 知道它必须调用该方法并使用结果。 在 JVM 上运行时,当应用程序启动时,将使用反射调用方法时进行类解析。myBeanmyBean()@Configuration@Beanspring-doc.cn

在创建本机映像时, Spring 以不同的方式运行。 它不是在运行时解析类和生成 bean 定义,而是在构建时进行。 一旦发现 Bean 定义,它们就会被处理并转换为源代码,供 GraalVM 编译器分析。@Configurationspring-doc.cn

Spring AOT 过程会将上面的配置类转换为如下所示的代码:spring-doc.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 定义的性质而异。

您可以在上面看到,生成的代码创建了与类等效的 bean 定义,但以 GraalVM 可以理解的直接方式创建。@Configurationspring-doc.cn

有一个 bean 的 bean 定义,一个 bean 的定义。 当需要一个实例时,将调用 a。 此供应商将在 bean 上调用该方法。myConfigurationmyBeanmyBeanBeanInstanceSuppliermyBean()myConfigurationspring-doc.cn

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

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

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

1.2.2. Hint 文件生成

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

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

提示文件是在 GraalVM 自动选取它们的位置下生成的。META-INF/native-imagespring-doc.cn

生成的提示文件可以在 使用 Maven 时和 Gradle 中找到。target/spring-aot/main/resourcesbuild/generated/aotResources

1.2.3. 代理类生成

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

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

与源代码生成不同,生成的字节码在调试应用程序时并不是特别有用。 但是,如果您需要使用工具检查文件的内容,您可以在 Maven 和 Gradle 中找到它们。.classjavaptarget/spring-aot/main/classesbuild/generated/aotClasses

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

现在我们已经很好地了解了 GraalVM Native Images 以及 Spring 预验证引擎的工作原理,我们可以看看如何创建应用程序。spring-doc.cn

构建 Spring Boot 本机映像应用程序有两种主要方法:spring-doc.cn

  • 使用 Spring Boot 对 Cloud Native Buildpacks 的支持来生成包含本机可执行文件的轻量级容器。spring-doc.cn

  • 使用 GraalVM Native Build Tools 生成原生可执行文件。spring-doc.cn

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

2.1. 示例应用程序

我们需要一个示例应用程序,我们可以使用它来创建我们的原生镜像。 对于我们的目的,“getting-started.html”部分中介绍的简单 “Hello World!” Web 应用程序就足够了。spring-doc.cn

概括地说,我们的主要应用程序代码如下所示:spring-doc.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.cn

2.2. 使用 buildpack 构建原生镜像

Spring Boot 包括对直接用于 Maven 和 Gradle 的本机映像的 buildpack 支持。 这意味着您只需键入一个命令,即可快速将合理的映像获取到本地运行的 Docker 守护程序中。 生成的映像不包含 JVM,而是静态编译本机映像。 这会导致图像更小。spring-doc.cn

用于映像的生成器是 。 它占用空间小,攻击面更小,但如果需要,您也可以在映像中使用或拥有更多可用工具。paketobuildpacks/builder-jammy-tiny:latestpaketobuildpacks/builder-jammy-base:latestpaketobuildpacks/builder-jammy-full:latest

2.2.1. 系统要求

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

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

2.2.2. 使用 Maven

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

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

您还应该在以下部分包含以下内容:<build> <plugins>spring-doc.cn

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

声明一个配置文件,用于配置创建本机映像所需的执行。 您可以使用命令行上的 标志激活配置文件。spring-boot-starter-parentnative-Pspring-doc.cn

如果您不想使用,则需要为 Spring Boot 插件中的目标和 Native Build Tools 插件中的目标配置执行。spring-boot-starter-parentprocess-aotadd-reachability-metadata

要构建图像,您可以在配置文件处于活动状态的情况下运行目标:spring-boot:build-imagenativespring-doc.cn

$ mvn -Pnative spring-boot:build-image

2.2.3. 使用 Gradle

Spring Boot Gradle 插件在应用 GraalVM Native Image 插件时自动配置 AOT 任务。 您应该检查您的 Gradle 构建是否包含包含 .pluginsorg.graalvm.buildtools.nativespring-doc.cn

只要应用了插件,该任务就会生成本机映像,而不是 JVM 映像。 您可以使用以下方法运行任务:org.graalvm.buildtools.nativebootBuildImagespring-doc.cn

$ gradle bootBuildImage

2.2.4. 运行示例

运行适当的构建命令后,Docker 镜像应该可用。 您可以使用以下方法启动应用程序:docker runspring-doc.cn

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

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

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

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

Hello World!

要正常退出应用程序,请按 。ctrl-cspring-doc.cn

2.3. 使用 Native Build Tools 构建 Native Image

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

2.3.1. 先决条件

要使用原生构建工具构建原生镜像,您的机器上需要有 GraalVM 发行版。 您可以在 Liberica Native Image Kit 页面上手动下载它,也可以使用 SDKMAN! 等下载管理器。spring-doc.cn

Linux 和 macOS

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

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

通过检查以下各项的输出来验证是否配置了正确的版本:java -versionspring-doc.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 上,请按照这些说明安装 GraalVMLiberica Native Image Kit 版本 22.3、Visual Studio 构建工具和 Windows SDK。 由于 Windows 相关的命令行最大长度,请确保使用 x64 本机工具命令提示符而不是常规 Windows 命令行来运行 Maven 或 Gradle 插件。spring-doc.cn

2.3.2. 使用 Maven

buildpack 支持一样,您需要确保您正在使用 for inherit the profile 并且使用了插件。spring-boot-starter-parentnativeorg.graalvm.buildtools:native-maven-pluginspring-doc.cn

在配置文件处于活动状态的情况下,您可以调用 goal 来触发编译:nativenative:compilenative-imagespring-doc.cn

$ mvn -Pnative native:compile

本机映像可执行文件可以在目录中找到。targetspring-doc.cn

2.3.3. 使用 Gradle

当 Native Build Tools Gradle 插件应用到您的项目时,Spring Boot Gradle 插件将自动触发 Spring AOT 引擎。 任务依赖关系是自动配置的,因此您只需运行标准任务即可生成本机映像:nativeCompilespring-doc.cn

$ gradle nativeCompile

本机映像可执行文件可以在目录中找到。build/native/nativeCompilespring-doc.cn

2.3.4. 运行示例

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

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

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

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

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

Hello World!

要正常退出应用程序,请按 。ctrl-cspring-doc.cn

3. 测试 GraalVM 原生镜像

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

对于本机映像测试,您通常希望确保以下方面正常工作:spring-doc.cn

  • Spring AOT 引擎能够处理您的应用程序,并且它将在 AOT 处理模式下运行。spring-doc.cn

  • GraalVM 有足够的提示来确保可以生成有效的原生镜像。spring-doc.cn

3.1. 使用 JVM 测试预先处理

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

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

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

要在 JVM 上运行 Spring Boot 应用程序并使其使用 AOT 生成的代码,可以将 system 属性设置为。spring.aot.enabledtruespring-doc.cn

例如:spring-doc.cn

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

如果应用程序开始时将属性设置为 ,则您更确信它在转换为本机映像时将正常工作。spring.aot.enabledtruespring-doc.cn

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

3.2. 使用原生构建工具进行测试

GraalVM Native Build Tools 能够在原生映像中运行测试。 当您想深入测试应用程序的内部结构是否在 GraalVM 原生映像中工作时,这可能很有帮助。spring-doc.cn

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

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

Spring Framework 的原生测试支持以以下方式工作:spring-doc.cn

  • 分析测试以发现所需的任何实例。ApplicationContextspring-doc.cn

  • 将提前处理应用于每个应用程序上下文,并生成资产。spring-doc.cn

  • 将创建一个原生镜像,生成的资产由 GraalVM 处理。spring-doc.cn

  • 本机映像还包括配置了已发现测试列表的 JUnit。TestEnginespring-doc.cn

  • 本机映像启动,触发将运行每个测试并报告结果的引擎。spring-doc.cn

3.2.1. 使用 Maven

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

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

声明一个配置文件,用于配置运行本机测试所需的执行。 您可以使用命令行上的 标志激活配置文件。spring-boot-starter-parentnativeTest-Pspring-doc.cn

如果您不想使用,则需要为 Spring Boot 插件中的目标和 Native Build Tools 插件中的目标配置执行。spring-boot-starter-parentprocess-test-aottest

要构建映像并运行测试,请在配置文件处于活动状态的情况下使用目标:testnativeTestspring-doc.cn

$ mvn -PnativeTest test

3.2.2. 使用 Gradle

Spring Boot Gradle 插件在应用 GraalVM Native Image 插件时自动配置 AOT 测试任务。 您应该检查您的 Gradle 构建是否包含包含 .pluginsorg.graalvm.buildtools.nativespring-doc.cn

要使用 Gradle 运行原生测试,您可以使用以下任务:nativeTestspring-doc.cn

$ gradle nativeTest

4. 高级原生映像主题

4.1. 嵌套配置属性

Spring 预配置引擎会自动为 configuration 属性创建反射提示。 但是,不是内部类的嵌套配置属性必须用 Comments ,否则它们不会被检测到并且不可绑定。@NestedConfigurationPropertyspring-doc.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;
    }

}

在哪里:Nestedspring-doc.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@NestedConfigurationPropertynestedmy.properties.nested.numberspring-doc.cn

使用构造函数绑定时,您必须使用 :@NestedConfigurationPropertyspring-doc.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;
    }

}

使用记录时,您必须使用 :@NestedConfigurationPropertyspring-doc.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 时,您需要使用以下代码注释数据类的参数:@NestedConfigurationPropertyspring-doc.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
)
请在所有情况下使用 public getter 和 setter,否则属性将不可绑定。

4.2. 转换 Spring Boot 可执行 Jar

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

  • 您可以保留常规的 JVM 管道,并将 JVM 应用程序转换为 CI/CD 平台上的本机映像。spring-doc.cn

  • 由于不支持交叉编译,因此您可以保留一个 OS 中立的部署工件,稍后将其转换为不同的 OS 架构。native-imagespring-doc.cn

您可以使用 Cloud Native Buildpacks 或使用 GraalVM 附带的工具将 Spring Boot 可执行 jar 转换为本机映像。native-imagespring-doc.cn

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

4.2.1. 使用 Buildpack

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

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

您还需要按照 buildpacks.io 上的安装指南进行安装。packspring-doc.cn

假设 AOT 处理的 Spring Boot 可执行 jar 按原样在目录中构建,请运行:myproject-0.0.1-SNAPSHOT.jartargetspring-doc.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 即可以这种方式生成映像。

完成后,您可以使用以下方法启动应用程序:packdocker runspring-doc.cn

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

4.2.2. 使用 GraalVM native-image

将 AOT 处理的 Spring Boot 可执行 jar 转换为本机可执行文件的另一种选择是使用 GraalVM 工具。 为此,您的机器上需要一个 GraalVM 发行版。 您可以在 Liberica Native Image Kit 页面上手动下载它,也可以使用 SDKMAN! 等下载管理器。native-imagespring-doc.cn

假设 AOT 处理的 Spring Boot 可执行 jar 按原样在目录中构建,请运行:myproject-0.0.1-SNAPSHOT.jartargetspring-doc.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 进行调整。
可能没有打包在您的 jar 中。 仅当需要可访问性元数据覆盖时,才会包含它。@META-INF/native-image/argfile
该标志不接受通配符。 您需要确保列出所有 jar(上面的命令使用 and 来执行此操作)。native-image-cpfindtr

4.3. 使用 Tracing Agent

GraalVM 原生图像跟踪代理允许您拦截 JVM 上的反射、资源或代理使用情况,以生成相关提示。 Spring 应该自动生成大部分这些提示,但是可以使用跟踪代理来快速识别缺少的条目。spring-doc.cn

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

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

第二个选项听起来更吸引可重复的设置,但默认情况下,生成的 Importing 将包括测试基础结构所需的任何内容。 当应用程序真正运行时,其中一些是不必要的。 为了解决这个问题,代理支持一个访问过滤器文件,这将导致某些数据被排除在生成的输出之外。spring-doc.cn

4.3.1. 直接启动应用程序

使用以下命令启动附加了本机图像跟踪代理的应用程序:spring-doc.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-cspring-doc.cn

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

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

4.4. 自定义提示

如果您需要为反射、资源、序列化、代理使用等提供自己的提示,则可以使用 API。 创建一个实现接口的类,然后对提供的实例进行适当的调用:RuntimeHintsRegistrarRuntimeHintsRegistrarRuntimeHintsspring-doc.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@SpringBootApplicationspring-doc.cn

如果您有需要绑定的类(主要是在序列化或反序列化 JSON 时需要),则可以在任何 bean 上使用 @RegisterReflectionForBinding。 大多数提示都是自动推断的,例如,当接受或返回来自方法的数据时。 但是,当您使用 或直接与 一起使用时,可能需要使用 .@RestControllerWebClientRestClientRestTemplate@RegisterReflectionForBindingspring-doc.cn

4.4.1. 测试自定义 Hint

API 可用于测试您的提示。 API 提供了构建可用于测试实例的方法。RuntimeHintsPredicatesPredicateRuntimeHintsspring-doc.cn

如果您使用的是 AssertJ,则您的测试将如下所示:spring-doc.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.4.2. 静态提供提示

如果您愿意,可以在一个或多个 GraalVM JSON 提示文件中静态提供自定义提示。 此类文件应放在目录中。 AOT 处理过程中生成的提示将写入名为 的目录。 将静态提示文件放在不与此位置冲突的目录中,例如src/main/resources/META-INF/native-image/*/*/META-INF/native-image/{groupId}/{artifactId}/META-INF/native-image/{groupId}/{artifactId}-additional-hints/spring-doc.cn

4.5. 已知限制

GraalVM 原生映像是一项不断发展的技术,并非所有库都提供支持。 GraalVM 社区通过为尚未发布自己的项目提供可访问性元数据来提供帮助。 Spring 本身不包含 3rd 方库的提示,而是依赖于可访问性元数据项目。spring-doc.cn

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

如果您发现某个库无法与 GraalVM 一起使用,请在可访问性元数据项目上提出问题。spring-doc.cn

5. 接下来要读什么

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

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

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