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

  • 创建原生镜像时无法访问的代码会被删除,且不会成为可执行文件的一部分。spring-doc.cadn.net.cn

  • GraalVM 不会直接感知你代码中的动态元素,必须被告知关于反射、资源、序列化和动态代理的相关信息。spring-doc.cadn.net.cn

  • 应用类路径在构建时是固定的,不能更改。spring-doc.cadn.net.cn

  • 没有懒惰类加载,所有可执行文件里发布的内容都会在启动时加载到内存中。spring-doc.cadn.net.cn

  • Java应用的某些方面存在一些未被完全支持的限制。spring-doc.cadn.net.cn

除了这些差异外,Spring还使用一种称为Spring提前处理的工艺,这对此施加了更多限制。 请务必至少阅读下一节的开头,了解这些信息。spring-doc.cadn.net.cn

GraalVM 参考文档中的原生映像兼容性指南部分提供了关于 GraalVM 限制的更多细节。

1.2. 理解春季提前处理

典型的 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 Framework 使用两种不同的概念来管理豆子。 有豆子实例,指的是被创建并可以注入其他豆子的实例。 还有Beans定义,用于定义豆子的属性及其实例应如何创建。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();
    }

}

豆子定义是通过解析@Configuration类并寻找@Bean方法。 在上述例子中,我们定义了一个豆子定义对于一个名为我的豆子. 我们还在创造一个豆子定义对于MyConfiguration课程本身。spring-doc.cadn.net.cn

我的豆子实例是必需的,Spring 知道它必须调用我的豆()方法并使用结果。 在JVM上运行时,@Configuration类解析发生在应用开始时,@Bean方法通过反射被调用。spring-doc.cadn.net.cn

在创建原生图像时,Spring 的运作方式不同。 而不是解析@Configuration在运行时生成类和生成 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;
    }

}
具体生成的代码可能会根据你豆子定义的性质而有所不同。

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

豆子对myConfiguration豆子,还有一个我的豆子. 当我的豆子需要实例,a豆实例提供商被叫去。 该提供商将调用我的豆()方法myConfiguration豆。spring-doc.cadn.net.cn

在春季AOT处理期间,你的申请会被启动,直到豆定义可用为止。 豆子实例不会在AOT处理阶段创建。

Spring AOT 会为你所有的 bean 定义生成类似的代码。 当需要豆后处理时(例如,调用)@Autowired方法)。 一应用上下文初始化器还将生成 ,Spring Boot 将用它初始化应用上下文当AOT处理后的应用程序实际运行时。spring-doc.cadn.net.cn

虽然AOT生成的源代码可能冗长,但相当易读,在调试应用程序时非常有用。 生成的源文件可在以下文件中找到目标/春季进攻/主线/来源当使用 Maven 和构建/生成/进攻的来源与Gradle合作。

1.2.2. 提示文件生成

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

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

提示文件由以下生成元-INF/原生图像这些数据会被 GraalVM 自动接收。spring-doc.cadn.net.cn

生成的提示文件可在目标/春季的攻击/主/资源当使用 Maven 和构建/生成/进攻资源与Gradle合作。

1.2.3. 代理类生成

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

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

与源代码生成不同,生成字节码在调试应用时并不特别有用。 然而,如果您需要检查。类使用以下工具的文件JavaP你可以在目标/春季-AOT/主/职业对于Maven和构建/生成/进攻的职业为了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.md文件会提供入门提示。

2.1. 示例应用

我们需要一个示例应用程序,可以用来创建我们的原生镜像。 就我们而言,“getting-started.html”部分介绍的简单“Hello World!”网页应用就足够了。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:最新. 它占地面积小,攻击面也更小,但你也可以使用Paketobuildpacks/builder-jammy-base:最新Paketobuildpacks/builder-jammy-full:最新以便在需要时在镜像中提供更多工具。

2.2.1. 系统需求

应该安装Docker。详情请参见Get Docker。如果你用的是Linux,可以设置成允许非root用户访问spring-doc.cadn.net.cn

你可以跑了Docker 运行 Hello-World(不须藤)以检查 Docker 守护进程是否可访问,如预期般。 更多细节可以查阅MavenGradle Spring Boot插件的文档。
在macOS上,建议至少将分配给Docker的内存增加到8GB,并且可能还会增加更多CPU。 更多细节请参见Stack Overflow的回答。 在 Microsoft Windows 上,确保启用 Docker WSL 2 后端以提升性能。

2.2.2. 使用 Maven

要用 Maven 构建原生图片容器,你应该确保你的pom.xml文件使用Spring靴启动父以及org.graalvm.buildtools:native-maven-plugin. 你应该有<家长>该部分看起来像这样: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靴启动父宣告本地配置配置文件,配置需要运行的执行内容以创建原生映像。 你可以用-P命令行标记。spring-doc.cadn.net.cn

如果你不想用Spring靴启动父你需要配置执行进程进攻的进攻Spring Boot插件中的目标和添加可达性元数据目标来自Native Build Tools插件。

要构建镜像,你可以运行Spring Boot:构建映像目标本地个人资料活跃:spring-doc.cadn.net.cn

$ mvn -Pnative spring-boot:build-image

2.2.3. 使用 Gradle

当应用GraalVM原生映像插件时,Spring Boot Gradle插件会自动配置AOT任务。你应该检查你的Gradle构建是否包含插件该块包括org.graalvm.buildtools.native.spring-doc.cadn.net.cn

只要org.graalvm.buildtools.native插件被应用,启动构建图像任务生成的是原生镜像,而不是 JVM 镜像。你可以用以下方式运行该任务:spring-doc.cadn.net.cn

$ gradle bootBuildImage

2.2.4. 运行示例

运行了相应的构建命令后,应该会有一个 Docker 镜像可用。你可以用以下方式启动你的应用程序Docker 运行: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应用快得多。

如果你打开网页浏览器本地主持人: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 原生映像包页面手动下载,或者使用像 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 版本: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

和构建包支持一样,你需要确保你使用的是Spring靴启动父为了继承本地剖面,且org.graalvm.buildtools:native-maven-plugin使用了插件。spring-doc.cadn.net.cn

本地配置文件激活时,你可以调用本地:编译目标触发原生图像汇编:spring-doc.cadn.net.cn

$ mvn -Pnative native:compile

本地镜像可执行文件可在目标目录。spring-doc.cadn.net.cn

2.3.3. 使用 Gradle

当 Native Build Tools 的 Gradle 插件应用到你的项目时,Spring Boot Gradle 插件会自动触发 Spring AOT 引擎。任务依赖关系是自动配置的,所以你只需运行标准原生编译生成原生图像的任务: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
格拉德勒
$ 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应用快得多。

如果你打开网页浏览器本地主持人: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

自从......原生图像编译阶段可能需要一段时间才能完成,有时在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来说,这意味着你应该用-非本地人以激活本地轮廓。 对于Gradle,你需要确保你的构建包含org.graalvm.buildtools.native插件。

如果你的申请以spring.aot.enabled属性设置为true那么你对转换成原生图像时能正常工作的信心会更高。spring-doc.cadn.net.cn

你也可以考虑对正在运行的应用程序进行集成测试。 比如,你可以用SpringWeb客户端调用你的应用程序 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靴启动父. 你应该有<家长>该部分看起来像这样:spring-doc.cadn.net.cn

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

Spring靴启动父宣告nativeTest配置运行本地测试所需的执行配置。 你可以用-P命令行标记。spring-doc.cadn.net.cn

如果你不想用Spring靴启动父你需要配置执行过程测试 AOTSpring Boot 插件中的目标和测试目标来自Native Build Tools插件。

要构建镜像并运行测试,请使用测试目标nativeTest个人资料活跃:spring-doc.cadn.net.cn

$ mvn -PnativeTest test

3.2.2. 使用 Gradle

当应用GraalVM原生映像插件时,Spring Boot Gradle插件会自动配置AOT测试任务。 你应该检查你的Gradle构建是否包含插件该块包括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;
    }

}

哪里嵌 套是: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关于嵌 套字段,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
)
请在所有情况下使用公开的获取者和设定者,否则属性将无法绑定。

4.2. 转换 Spring Boot 可执行 jar

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

你可以用Cloud Native Buildpacks将Spring Boot的可执行文件jar转换成原生镜像,或者使用原生图像该工具随GraalVM一同发布。spring-doc.cadn.net.cn

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

4.2.1. 构建包的使用

Spring Boot 应用程序通常通过 Maven 使用 Cloud Native 构建包(MVN Spring-boot:build-image)或Gradle(gradle bootBuildImage)集成。不过,你也可以使用将AOT处理的Spring Boot可执行文件jar转换为本地容器镜像。spring-doc.cadn.net.cn

首先,确保有 Docker 守护进程可用(详情请参见获取 Docker )。如果你用的是 Linux,可以配置成允许非 root 用户访问spring-doc.cadn.net.cn

你还需要安装通过按照 buildpacks.io 上的安装指南作。spring-doc.cadn.net.cn

假设AOT处理了Spring Boot可执行文件jar,构建为myproject-0.0.1-SNAPSHOT.jar属于目标目录,运行: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 安装就能以这种方式生成映像。

一次完成后,你可以用以下方式启动应用Docker 运行: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转换为原生可执行文件的另一种方法是使用GraalVM原生图像工具。 要实现这一点,你需要在你的机器上安装 GraalVM 发行版。你可以在 Liberica 原生映像包页面手动下载,或者使用像 SDKMAN! 这样的下载管理器。spring-doc.cadn.net.cn

假设AOT处理了Spring Boot可执行文件jar,构建为myproject-0.0.1-SNAPSHOT.jar属于目标目录,运行: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里。只有在需要覆盖可达元数据时才会包含它。
原生图像 -cpflag 不接受万用符。你需要确保所有 jar 都被列出(上述命令使用找到TR做到这一点)。

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然后对所提供的 进行适当调用。运行提示实例: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方法。 但当你与Web客户端,Rest客户端Rest模板直接来说,你可能需要使用@RegisterReflectionForBinding.spring-doc.cadn.net.cn

4.4.1. 测试自定义提示

运行提示谓词API可以用来测试你的提示。 该 API 提供了构建谓语可以用来测试运行提示实例。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/元-INF/原生图像/*/*/目录。 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本身不包含第三方库的提示,而是依赖可达性元数据项目。spring-doc.cadn.net.cn

如果您在为 Spring Boot 应用程序生成原生镜像时遇到问题,请查看 Spring Boot 维基中的 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 维基spring-doc.cadn.net.cn