GraalVM Native Images是可以通过提前处理编译的Java应用程序生成的独立可执行文件。本机映像通常比JVM映像占用的内存更少,启动速度也更快。

1. Introducing GraalVM Native Images

GraalVM Native Images提供了一种部署和运行Java应用程序的新方法。与Java虚拟机相比,本机映像可以使用更小的内存空间和更快的启动时间运行。

它们非常适合使用容器镜像部署的应用程序,当与“功能即服务”(FAAS)平台结合使用时尤其有趣。

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

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

If you just want to get started and experiment with GraalVM you can skip ahead to the “Developing Your First GraalVM Native Application” section and return to this section later.

1.1. Key Differences with JVM Deployments

GraalVM本机映像是提前生成的,这一事实意味着本机应用程序和基于JVM的应用程序之间存在一些关键区别。主要区别是:

  • 应用程序的静态分析在构建时从main入口点执行。

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

  • GraalVM不直接知道代码的动态元素,必须被告知反射、资源、序列化和动态代理。

  • 应用程序类路径在生成时是固定的,不能更改。

  • 没有懒惰的类加载,可执行文件中的所有内容都将在启动时加载到内存中。

  • Java应用程序的某些方面存在一些限制,这些限制并不完全受支持。

The Native Image Compatibility Guide section of the GraalVM reference documentation provide more details about GraalVM limitations.

1.2. Understanding Spring Ahead-of-Time Processing

典型的Spring Boot应用程序是非常动态的,并且配置在运行时执行。事实上,Spring Boot自动配置的概念在很大程度上依赖于对运行时状态的反应,以便正确地进行配置。

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

封闭世界假设意味着以下限制:

  • 类路径是固定的,并且在生成时完全定义

  • 应用程序中定义的Bean不能在运行时更改,这意味着:

    • 不支持Spring@Profile批注和特定于配置文件的配置

    • 不支持在创建Bean时更改的属性(例如,@ConditionalOnProperty.able属性)。

当这些限制就位时,Spring就可以在构建时执行提前处理,并生成GraalVM可以使用的额外资产。经过Spring AOT处理的应用程序通常会生成:

  • Java源代码

  • 字节码(用于动态代理等)

  • GraalVM JSON提示文件:

    • 资源提示(resource-config.json)

    • 反射提示(Reflect-config.json)

    • 序列化提示(Serialization-config.json)

    • Java代理提示(Proxy-config.json)

    • JNI提示(jni-config.json)

1.2.1. Source Code Generation

Spring应用程序由SpringBean组成。在内部,Spring框架使用两个截然不同的概念来管理Bean。有一些Bean实例,它们是已经创建并可以注入到其他Bean中的实际实例。还有一些Bean定义,用于定义Bean的属性及其实例的创建方式。

如果我们使用一个典型的@Configuration类:

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方法来创建Bean定义。在上面的示例中,我们为名为myBean的单个Bean定义了BeanDefinition。我们还为MyConfiguration类本身创建了一个BeanDefinition

当需要myBean实例时,Spring知道它必须调用myBean()方法并使用结果。在JVM上运行时,应用程序启动时会进行@configuration类解析,并使用反射调用@Bean方法。

在创建本机映像时,Spring以不同的方式运行。它不是在运行时解析@configuration类并生成Bean定义,而是在构建时进行。一旦发现了Bean定义,就会对其进行处理并将其转换为可由GraalVM编译器分析的源代码。

Spring AOT过程会将上面的配置类转换为如下代码:

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; } } 
             
             
The exact code generated may differ depending on the nature of your bean definitions.

您可以在上面看到,生成的代码创建了与@Configuration类等价的Bean定义,但以GraalVM可以理解的直接方式创建。

myConfigurationBean有一个Bean定义,myBean有一个Bean定义。当需要myBean实例时,将调用BeanInstanceSupplier。该供应商将对myConfigurationBean调用myBean()方法。

During Spring AOT processing your application is started up to the point that bean definitions are available. Bean instances are not created during the AOT processing phase.

Spring AOT将为您的所有Bean定义生成类似这样的代码。它还将在需要Bean后处理时生成代码(例如,调用@Autwire方法)。还将生成一个ApplicationContextInitializer,当AOT处理的应用程序实际运行时,Spring Boot将使用它来初始化ApplicationContext

Although AOT generated source code can be verbose, it is quite readable and can be helpful when debugging an application. Generated source files can be found in target/spring-aot/main/sources when using Maven and build/generated/aotSources with Gradle.

1.2.2. Hint File Generation

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

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

提示文件在META-INF/Native-Image下生成,其中它们由GraalVM自动拾取。

Generated hint files can be found in target/spring-aot/main/resources when using Maven and build/generated/aotResources with Gradle.

1.2.3. Proxy Class Generation

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

当应用程序在JDK上运行时,代理类在应用程序运行时动态生成。在创建本机映像时,需要在构建时创建这些代理,以便GraalVM可以包括它们。

Unlike source code generation, generated bytecode isn’t particularly helpful when debugging an application. However, if you need to inspect the contents of the .class files using a tool such as javap you can find them in target/spring-aot/main/classes for Maven and build/generated/aotClasses for Gradle.

2. Developing Your First GraalVM Native Application

现在我们已经很好地概述了GraalVM Native Images以及Spring Ahead-Time引擎是如何工作的,接下来我们来看看如何创建应用程序。

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

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

  • 使用GraalVM本机构建工具生成本机可执行文件。

The easiest way to start a new native Spring Boot project is to go to start.spring.io, add the “GraalVM Native Support” dependency and generate the project. The included HELP.md file will provide getting started hints.

2.1. Sample Application

我们需要一个示例应用程序,我们可以使用它来创建我们的本机映像。就我们的目的而言,简单的“Hello World!”“Get-started.html一节中介绍的Web应用程序就足够了。

简单地说,我们的主要应用程序代码如下所示:

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和Embedded Tomcat,这两个应用程序都已经过测试和验证,可以使用GraalVM本机映像。

2.2. Building a Native Image Using Buildpacks

Spring Boot包括对Maven和Gradle的本机镜像的构建包支持。这意味着您只需输入一条命令,即可在本地运行的Docker守护程序中快速获取一个合理的图像。生成的映像不包含JVM,而是静态编译本机映像。这会导致图像变小。

The builder used for the images is paketobuildpacks/builder:tiny. It has small footprint and reduced surface attack, but you can also use paketobuildpacks/builder:base or paketobuildpacks/builder:full to have more tools available in the image if required.

2.2.1. System Requirements

需要安装Docker,详情请参考获取Docker将其配置为允许非超级用户(如果您使用的是Linux)。

You can run docker run hello-world (without sudo) to check the Docker daemon is reachable as expected. Check the Maven or Gradle Spring Boot plugin documentation for more details.
On MacOS, it is recommended to increase the memory allocated to Docker to at least 8GB, and potentially add more CPUs as well. See this Stackoverflow answer for more details. On Microsoft Windows, make sure to enable the Docker WSL 2 backend for better performance.

2.2.2. Using Maven

要使用Maven构建本机映像容器,您应该确保您的pom.xml文件使用SpringBoot-starter-parent。您应该具有如下所示的<;Parent<;部分:

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

SpringBoot-starter-parent声明了一个本机配置文件,该配置文件配置了创建本机映像所需运行的执行。您可以在命令行中使用-P标志来激活配置文件。

If you don’t want to use spring-boot-starter-parent you’ll need to configure executions for the process-aot goal from Spring Boot’s plugin and the add-reachability-metadata goal from the Native Build Tools plugin.

要构建映像,您可以在本机配置文件处于活动状态的情况下运行SpringBoot:Build-Image目标:

$ mvn -Pnative spring-boot:build-image
             
             

2.2.3. Using Gradle

当应用GraalVM Native Image插件时,Spring Boot Gradle插件会自动配置AOT任务。您应该检查您的Gradle构建是否包含一个plugins块,该块包括org.graalvm.Buildtools.ative

只要应用org.graalvm.Buildtools.ative插件,bootBuildImage任务就会生成本机映像,而不是JVM映像。您可以使用以下命令运行该任务:

$ gradle bootBuildImage
             
             

2.2.4. Running the example

一旦您运行了适当的构建命令,就应该可以使用Docker映像了。您可以使用docker run启动应用程序:

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

您应该会看到类似于以下内容的输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.0.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
             
             
The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM.

如果您打开Web浏览器进入localhost:8080,您应该会看到以下输出:

Hello World!

要正常退出应用程序,请按ctrl-c

2.3. Building a Native Image using Native Build Tools

如果您想不使用Docker直接生成本地可执行文件,您可以使用GraalVM Native Build Tools。本地构建工具是GraalVM为Maven和Gradle提供的插件。您可以使用它们来执行各种GraalVM任务,包括生成本机映像。

2.3.1. Prerequisites

要使用本机构建工具构建本机映像,您的机器上需要安装GraalVM发行版。您可以在Liberica Native Image Kit页面手动下载它,也可以使用SDKMAN!这样的下载管理器。

Linux and MacOS

要在MacOS或Linux上安装本机映像编译器,我们建议使用SDKMAN!。抓住SDKMAN!使用以下命令从sdkman.io安装Liberica GraalVM发行版:

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

通过检查Java-Version的输出,验证是否配置了正确的版本:

$ 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

在Windows上,按照以下说明安装版本22.3中的GraalVM或Liberica Native Image Kit、Visual Studio生成工具和Windows SDK。由于与Windows相关的命令行最大长度,请确保使用x64 Native Tools命令提示符而不是常规的Windows命令行来运行Maven或Gradle插件。

2.3.2. Using Maven

构建包支持一样,为了继承本机配置文件,您需要确保您使用的是SpringBoot-starter-Parent

原生配置文件处于活动状态的情况下,您可以调用Native:Compile目标来触发原生映像编译:

$ mvn -Pnative native:compile
             
             

可以在目标目录中找到本机映像可执行文件。

2.3.3. Using Gradle

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

$ gradle nativeCompile
             
             

本机映像可执行文件可以在build/ative/nativeCompile目录中找到。

2.3.4. Running the Example

此时,您的应用程序应该可以工作了,您现在可以通过直接运行它来启动该应用程序:

Maven
Gradle
$ target/myproject
             
             

您应该会看到类似于以下内容的输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.0.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
             
             
The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM.

如果您打开Web浏览器进入localhost:8080,您应该会看到以下输出:

Hello World!

要正常退出应用程序,请按ctrl-c

3. Testing GraalVM Native Images

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

对于本机映像测试,您通常希望确保以下几个方面的工作:

  • Spring AOT引擎能够处理您的应用程序,并且它将以AOT处理模式运行。

  • GraalVM有足够的提示来确保可以生成有效的本机映像。

3.1. Testing Ahead-of-time Processing With the JVM

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

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

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

要在JVM上运行一个Spring Boot应用程序并使其使用AOT生成的代码,可以将spring.aot.Enabled系统属性设置为true

例如:

$ java -Dspring.aot.enabled=true -jar myapplication.jar
            
            
You need to ensure that the jar you are testing includes AOT generated code. For Maven, this means that you should build with -Pnative to active the native profile. For Gradle, you need to ensure that your build includes the org.graalvm.buildtools.native plugin.

如果您的应用程序在启动时将spring.aot.Enabled属性设置为true,那么您可以更有把握地相信,当转换为本机映像时,它将正常工作。

您还可以考虑对正在运行的应用程序运行集成测试。例如,您可以使用SpringWebClient将应用程序称为REST端点。或者,您可以考虑使用Selify这样的项目来检查您的应用程序的HTML响应。

3.2. Testing With Native Build Tools

GraalVM本机构建工具包括在本机映像中运行测试的能力。当您想要深入测试应用程序的内部在GraalVM本机映像中工作时,这会很有帮助。

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

Spring框架包括对运行测试的提前支持。所有常见的Spring测试功能都适用于本机映像测试。例如,您可以继续使用@SpringBootTest注释。您还可以使用Spring Boot测试切片来仅测试应用程序的特定部分。

Spring框架的本机测试支持通过以下方式工作:

  • 测试进行分析,以发现所需的任何ApplicationContext实例。

  • 对这些应用程序上下文中的每一个应用程序应用预先处理,并生成资产。

  • 创建本机映像,生成的资产由GraalVM处理。

  • 本机映像还包括配置了已发现测试列表的JUnitTestEngine

  • 启动本机映像,触发引擎,该引擎将运行每项测试并报告结果。

3.2.1. Using Maven

要使用Maven运行本机测试,请确保您的pom.xml文件使用Spring-ot-starter-parent。您应该具有如下所示的<;Parent<;部分:

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

SpringBoot-starter-parent声明了一个nativeTest配置文件,该配置文件配置了运行本机测试所需的执行。您可以在命令行中使用-P标志来激活配置文件。

If you don’t want to use spring-boot-starter-parent you’ll need to configure executions for the process-test-aot goal from the Spring Boot plugin and the test goal from the Native Build Tools plugin.

要构建映像并运行测试,请使用测试目标,并激活nativeTest配置文件:

$ mvn -PnativeTest test

3.2.2. Using Gradle

当应用GraalVM Native Image插件时,Spring Boot Gradle插件会自动配置AOT测试任务。您应该检查您的Gradle构建是否包含一个plugins块,该块包括org.graalvm.Buildtools.ative

要使用Gradle运行本机测试,您可以使用nativeTest任务:

$ gradle nativeTest

4. Advanced Native Images Topics

4.1. Nested Configuration Properties

由Spring Ahead-Time引擎自动为配置属性创建反射提示。但是,不是内部类的嵌套配置属性必须使用@NestedConfigurationProperty进行批注,否则它们将不会被检测到并且无法绑定。

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

其中嵌套为:

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属性将无法在本机映像中绑定。

使用构造函数绑定时,必须使用@NestedConfigurationProperty注释该字段:

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注释参数:

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注释数据类的参数:

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 ) 
            
            
Please use public getters and setters in all cases, otherwise the properties will not be bindable.

4.2. Converting a Spring Boot Executable Jar

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

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

  • 由于原生映像不支持交叉编译,您可以保留一个与操作系统无关的部署构件,以后可以将其转换为不同的操作系统体系结构。

您可以使用Cloud Native BuildPack或GraalVM附带的原生映像工具将Spring Boot可执行JAR转换为本机映像。

Your executable jar must include AOT generated assets such as generated classes and JSON hint files.

4.2.1. Using Buildpacks

Spring Boot应用程序通常通过Maven(MVN SpringBoot:Build-Image)或Gradle(Gradle bootBuildImage)集成使用Cloud Native BuildPack。但是,您也可以使用pack将AOT处理过的Spring Boot可执行JAR转换为本机容器镜像。

首先,确保Docker守护进程可用(有关详细信息,请参阅获取Docker)。将其配置为允许非超级用户(如果您使用的是Linux)。

您还需要按照Buildpack s.io上的安装指南安装pack

假设构建为myproject-0.0.1-SNAPSHOT.jar的AOT处理过的Spring Boot可执行JAR位于目标目录中,运行:

$ pack build --builder paketobuildpacks/builder:tiny \ --path target/myproject-0.0.1-SNAPSHOT.jar \ --env 'BP_NATIVE_IMAGE=true' \ my-application:0.0.1-SNAPSHOT
             
             
You do not need to have a local GraalVM installation to generate an image in this way.

完成打包后,可以使用docker run启动应用程序:

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

4.2.2. Using GraalVM native-image

将经过AOT处理的Spring Boot可执行JAR文件转换为本机可执行文件的另一种选择是使用GraalVM原生映像工具。要实现这一点,您的机器上需要安装GraalVM发行版。您可以在Liberica Native Image Kit页面上手动下载它,也可以使用SDKMAN!这样的下载管理器。

假设构建为myproject-0.0.1-SNAPSHOT.jar的AOT处理过的Spring Boot可执行JAR位于目标目录中,运行:

$ 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 ../
             
             
These commands work on Linux or MacOS machines, you will need to adapt them for Windows.
The @META-INF/native-image/argfile might not be packaged in your jar. It is only included when reachability metadata overrides are needed.
The native-image -cp flag does not accept wildcards. You need to ensure that all jars are listed (the command above uses find and tr to do this).

4.3. Using the Tracing Agent

GraalVM本机映像跟踪代理允许您拦截JVM上的反射、资源或代理使用情况,以便生成相关提示。Spring应该会自动生成这些提示中的大多数,但是可以使用跟踪代理来快速识别丢失的条目。

使用代理为本机映像生成提示时,有两种方法:

  • 直接启动应用程序并执行它。

  • 运行应用程序测试以测试应用程序。

当Spring无法识别库或模式时,第一个选项用于识别丢失的提示。

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

4.3.1. Launch the Application Directly

使用以下命令启动附加了本机映像跟踪代理的应用程序:

$ 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停止应用程序。

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

在原生镜像跟踪代理上可以设置更高级的选项,例如根据调用者类过滤录制的提示等。详细阅读请参阅官方文档

4.4. Custom Hints

如果您需要为反射、资源、序列化、代理使用等提供您自己的提示,可以使用RunmeHintsRegisterAPI。创建一个实现RounmeHintsRegister接口的类,然后适当调用所提供的RounmeHints实例:

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

然后,您可以在任何@Configuration类(例如您的@SpringBootApplication注释的应用程序类)上使用@ImportRounmeHints来激活这些提示。

如果您有需要绑定的类(在序列化或反序列化JSON时最需要),您可以在任何href=“0”>@RegisterReflectionForBinding上使用@RestController方法的数据时。但当您直接使用WebClientRestTemplate时,可能需要使用RegisterReflectionForBinding

4.4.1. Testing custom hints

RounmeHintsPredicates接口可用于测试您的提示。该API提供了构建谓词的方法,该方法可用于测试RounmeHints实例。

如果您使用的是AssertJ,您的测试将如下所示:

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. Known Limitations

GraalVM本机映像是一项不断发展的技术,并不是所有的库都提供支持。GraalVM社区通过为尚未发布自己的项目提供可达性元数据来提供帮助。Spring本身并不包含对第三方库的提示,而是依赖于可达性元数据项目。

如果您在为Spring Boot应用程序生成本机映像时遇到问题,请查看Spring Boot wiki的Spring Boot with GraalVM页面。您还可以向GitHub上的Spring-aot-Smoke-test项目提出问题,该项目用于确认常见应用程序类型是否按预期工作。

如果您发现某个库不能与GraalVM一起使用,请在可访问性元数据项目上提出问题。

5. What to Read Next

如果您想更多地了解我们的构建插件提供的提前处理,请参阅MavenGradle插件文档。要了解用于执行处理的API的更多信息,请浏览Spring框架源代码的org.springfrawork.aot.Generate org.springframework.beans.factory.aot>包。

有关Spring和GraalVM的已知限制,请参阅Spring Boot wiki

下一节将继续介绍Spring Boot CLI