GraalVM Native Image 支持

1. GraalVM 原生镜像简介

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

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

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

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

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

1.1. 与 JVM 部署的主要区别

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

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

如果生成的提示不够,您还可以提供自己的提示。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方法使用 Reflection 调用。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. Hint 文件生成

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

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

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

生成的 Hint 文件可以在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 Native Images 以及 Spring 预验证引擎的工作原理,我们可以看看如何创建应用程序。spring-doc.cadn.net.cn

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

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

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

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

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. 使用 buildpack 构建原生镜像

Spring Boot 包括对直接用于 Maven 和 Gradle 的本机映像的 buildpack 支持。 这意味着您只需键入一个命令,即可快速将合理的映像获取到本地运行的 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.xmlfile 使用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.2.12</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-aotgoal 和add-reachability-metadata目标。

要构建镜像,您可以运行spring-boot:build-imagegoal 替换为native配置文件处于活动状态:spring-doc.cadn.net.cn

$ mvn -Pnative spring-boot:build-image

2.2.3. 使用 Gradle

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

只要org.graalvm.buildtools.nativeplugin 时,使用bootBuildImagetask 将生成本机映像而不是 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.2.12)
....... . . .
....... . . . (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. 使用 Native Build Tools 构建 Native Image

如果您想在不使用 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 上,请按照这些说明安装 GraalVMLiberica Native Image Kit 版本 22.3、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-pluginplugin 的 intent 值。spring-doc.cadn.net.cn

使用native配置文件处于活动状态,您可以调用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 引擎。 任务依赖关系是自动配置的,因此您只需运行标准的nativeCompiletask 生成原生镜像:spring-doc.cadn.net.cn

$ gradle nativeCompile

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

2.3.4. 运行示例

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

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

您应该会看到类似于以下内容的输出:spring-doc.cadn.net.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: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.enabledsystem 属性设置为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 Native Build Tools 能够在原生映像中运行测试。 当您想深入测试应用程序的内部结构是否在 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.xmlfile 使用spring-boot-starter-parent. 您应该有一个<parent>部分,如下所示:spring-doc.cadn.net.cn

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

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

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

要构建镜像并运行测试,请使用testgoal 替换为nativeTest配置文件处于活动状态:spring-doc.cadn.net.cn

$ mvn -PnativeTest test

3.2.2. 使用 Gradle

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

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

$ gradle nativeTest

4. 高级原生映像主题

4.1. 嵌套配置属性

Spring 预配置引擎会自动为 configuration 属性创建反射提示。 但是,不是内部类的嵌套配置属性必须使用@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
)
请在所有情况下使用 public getter 和 setter,否则属性将不可绑定。

4.2. 转换 Spring Boot 可执行 Jar

只要 Spring Boot 可执行 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.jar位于target目录中,运行: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 native-image

将 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.jar位于target目录中,运行: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 -cpflag 不接受通配符。 您需要确保列出所有 jar(上面的命令使用findtr执行此作)。

4.3. 使用 Tracing Agent

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

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

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

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

在应用程序关闭时,本机映像跟踪代理会将提示文件写入给定的 config 输出目录。 您可以手动检查这些文件,也可以将它们用作本机映像构建过程的输入。 要将它们用作输入,请将它们复制到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类(例如,您的@SpringBootApplicationannotated application 类)来激活这些提示。spring-doc.cadn.net.cn

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

4.4.1. 测试自定义 Hint

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.4.2. 静态提供提示

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

4.5. 已知限制

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