本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
GraalVM Native Image 是独立的可执行文件,可以通过提前处理编译好的Java应用程序来生成。一般来说,Native Image的内存占用较小,启动速度也比JVM的同类产品快。
1. GraalVM 原生镜像的介绍
GraalVM原生镜像为部署和运行Java应用程序提供了一种新的方式。与Java虚拟机相比,原生镜像可以以更小的内存占用和更快的启动时间运行。
它们非常适用于使用容器镜像部署的应用程序,当与 "功能即服务"(FaaS)平台结合时,尤其令人感兴趣。
与为JVM编写的传统应用程序不同,GraalVM Native Image 应用程序需要提前处理,以创建可执行文件。这种超前处理包括从主入口点静态地分析你的应用程序代码。
GraalVM本地镜像是一个完整的、针对平台的可执行文件。你不需要为了运行一个本地镜像而去运行一个Java虚拟机。
如果你只是想开始使用GraalVM并进行实验,你可以跳到 “开发你的第一个 GraalVM 原生应用程序” 部分,以后再回到这部分。 |
1.1. 与JVM部署的主要区别
GraalVM原生镜像是提前制作的,这意味着原生和基于JVM的应用程序之间存在一些关键的差异。 主要的区别是。
-
对应用程序的静态分析是在构建时从
main
入口点进行的。 -
在创建本地镜像时无法到达的代码将被删除,不会成为可执行文件的一部分。
-
GraalVM不能直接知道你的代码中的动态元素,必须被告知反射、资源、序列化和动态代理的情况。
-
应用程序的classpath在构建时是固定的,不能改变。
-
没有类的延迟加载,可执行文件中的所有内容都将在启动时加载到内存中。
-
围绕Java应用程序的某些方面有一些限制,不完全支持。
GraalVM参考文档中的 Native Image Compatibility Guide(原生镜像兼容指南) 部分提供了关于GralVM限制的更多细节。 |
1.2. 了解Spring的提前处理(Ahead-of-Time Processing)
典型的Spring Boot应用程序是相当动态的,配置是在运行时进行的。事实上,Spring Boot自动配置的概念在很大程度上取决于对运行时状态的反应,以便正确配置。
虽然可以告诉GraalVM关于应用程序的这些动态方面,但这样做会破坏静态分析的大部分好处。因此,在使用Spring Boot创建本地镜像时,我们假设了一个封闭的世界,应用程序的动态方面受到了限制。
一个封闭世界的假设意味着以下限制。
-
classpath是固定的,在构建时完全定义。
-
在你的应用程序中定义的Bean不能在运行时改变,也就是说。
-
Spring 的
@Profile
注解和 profile 的特定配置,有一些限制。 -
不支持创建Bean时发生变化的属性(例如,
@ConditionalOnProperty
和.enable
属性)。
-
当这些限制到位后,Spring就有可能在构建时进行提前处理,并生成GraalVM可以使用的额外资源。一个经过Spring AOT处理的应用程序通常会生成。
-
Java 源代码
-
字节码(用于动态代理等)
-
GraalVM的JSON hint文件。
-
资源 hint(
resource-config.json
) -
反射 hint(
reflect-config.json
) -
序列化 hint(
serialization-config.json
)。 -
Java代理 hint (
proxy-config.json
) -
JNI hint (
jni-config.json
)
-
1.2.1. 源代码生成
Spring应用程序是由Spring Bean组成的。在内部,Spring框架使用两个不同的概念来管理Bean。一个是Bean实例,它是已经创建的实际实例,可以被注入到其他Bean中。还有一个Bean定义,用于定义Bean的属性以及如何创建其实例。
如果我们采取一个典型的 @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 process 将把上面的配置类转换为这样的代码。
/**
* 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
类相当的Bean定义,但是是以一种GraalVM可以理解的直接方式。
有一个针对 myConfiguration
Bean的Bean定义,还有一个针对 myBean
的定义。当需要一个 myBean
实例时,会调用一个 BeanInstanceSupplier
。这个supplier将调用 myConfiguration
Bean上的 myBean()
方法。
在Spring AOT处理过程中,你的应用程序被启动到可以使用Bean定义(bean definition)的程度。在AOT处理阶段,不会创建bean实例。 |
Spring AOT将为你的所有Bean定义生成这样的代码。当需要对Bean进行后处理(post-processing)时,它也会生成代码(例如,调用 @Autowired
方法)。一个 ApplicationContextInitializer
也将被生成,当AOT处理的应用程序实际运行时,Spring Boot将使用它来初始化 ApplicationContext
。
虽然AOT生成的源代码可能很冗长,但可读性很强,在调试应用程序时很有帮助。使用Maven时,生成的源文件可以在 target/spring-aot/main/sources 中找到,使用Gradle时可以在 build/generated/aotSources 中找到。
|
1.2.2. Hint 文件的生成
除了生成源文件,Spring AOT引擎还将生成hint文件,供GraalVM使用。hint文件包含JSON数据,描述了GraalVM应该如何处理它无法通过直接检查代码来理解的东西。
例如,你可能在一个私有方法上使用Spring注解。为了调用私有方法,Spring需要使用反射,即使是在GraalVM上。当出现这种情况时,Spring可以编写一个反射hint,这样GraalVM就知道即使没有直接调用该私有方法,它仍然需要在本地镜像中可用。
Hint文件是在 META-INF/native-image
下生成的,GraalVM会自动接收这些文件。
使用Maven时,生成的hint文件可以在 target/spring-aot/main/resources 中找到,使用Gradle时可以在 build/generated/aotResources 中找到。
|
2. 开发你的第一个 GraalVM 原生应用程序
现在我们已经对 GraalVM Native Images 和Spring超前引擎(ahead-of-time engine)的工作方式有了一个很好的概述,我们可以看看如何创建一个应用程序。
构建Spring Boot原生镜像应用程序有两种主要方式。
-
使用Spring Boot对Cloud Native Buildpacks的支持,生成一个包含原生可执行文件的轻量级容器。
-
使用GraalVM本地构建工具来生成本地可执行文件。
启动一个新的本地Spring Boot项目的最简单方法是进入 https://start.springboot.io,添加 “GraalVM Native Support” 依赖并生成项目。包含的 HELP.md 文件将提供入门提示。
|
2.1. 示例应用
我们需要一个可以用来创建原生镜像的示例应用程序。就我们的目的而言,“getting-started.html” 部分所涉及的简单的 "Hello World" web应用就足够了。
回顾一下,我们的 main application 代码看起来是这样的。
@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原生镜像一起使用。
2.2. 使用Buildpacks构建原生镜像
Spring Boot包括直接针对Maven和Gradle的原生镜像的buildpack支持。这意味着你只需输入一条命令,就能迅速将一个合理的镜像放入本地运行的Docker守护程序中。由此产生的镜像不包含JVM,而是静态地编译本地镜像。这导致了更小的镜像。
用于镜像的构建器是 paketobuildpacks/builder:tiny 。它占用空间小,攻击面(surface attack)小,但你也可以使用 paketobuildpacks/builder:base 或 paketobuildpacks/builder:full ,以便在需要时在镜像中提供更多工具。
|
2.2.1. 系统要求
应该安装Docker,更多细节见 Get Docker。如果你是在Linux上,将其 配置为允许非root用户。
你可以运行 docker run hello-world (不用 sudo )来检查Docker守护进程是否能达到如期效果。查看 Maven 或 Gradle Spring Boot插件文档,了解更多细节。
|
在macOS上,建议将分配给Docker的内存增加到至少 8GB ,并可能增加更多的CPU。更多细节请参见 Stack Overflow 的回答 。在Microsoft Windows上,确保启用 Docker WSL 2 backend 以获得更好的性能。
|
2.2.2. 使用 Maven
要使用Maven构建一个本地镜像容器,你应该确保你的 pom.xml
文件使用 spring-boot-starter-parent
和 org.graalvm.buildtools:native-maven-plugin
。你应该有一个 <parent>
部分,看起来像这样。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>
你还应该在 <build> <plugins>
部分有这个:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
spring-boot-starter-parent
声明了一个 native
配置文件,配置了为创建原生镜像而需要运行的执行程序。你可以使用命令行上的 -P
标志激活配置文件。
如果你不想使用 spring-boot-starter-parent ,你就需要为Spring Boot的插件的 process-aot goal 和 Native Build Tools 插件的 add-reachability-metadata goal 配置 executions。
|
为了构建镜像,你可以在 native
配置文件激活的情况下运行 spring-boot:build-image
goal。
$ mvn -Pnative spring-boot:build-image
2.2.3. 使用 Gradle
当应用GraalVM Native Image插件时,Spring Boot Gradle插件会自动配置AOT任务。你应该检查你的Gradle构建中是否包含一个 plugins
块,其中包括 org.graalvm.buildtools.native
。
只要应用 了 org.graalvm.buildtools.native
插件,bootBuildImage
任务就会生成一个原生镜像,而不是JVM的。你可以用以下方式运行该任务
$ gradle bootBuildImage
2.2.4. 运行该例子
一旦你运行了相应的构建命令,就应该有一个Docker镜像了。你可以使用 docker run
启动你的应用程序。
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT
你应该看到类似以下的输出。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0-SNAPSHOT)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
启动时间因机器而异,但应该比运行在JVM上的Spring Boot应用快得多。 |
如果你打开web浏览器,访问 localhost:8080
,你应该看到以下输出。
Hello World!
要优雅地退出应用程序,按 ctrl-c
。
2.3. 使用 Native Build Tools 构建原生镜像
如果你想不使用Docker而直接生成本地可执行文件,你可以使用GraalVM的本地构建工具。本地构建工具是由GraalVM为Maven和Gradle提供的插件。你可以用它们来执行各种GraalVM任务,包括生成原生镜像。
2.3.1. 先决条件
要使用本地构建工具构建本地镜像,你需要在你的机器上有一个GraalVM发行版。你可以在 Liberica Native Image Kit页面 上手动下载,也可以使用SDKMAN这样的下载管理器!。
Linux 和 macOS
要在macOS或Linux上安装原生镜像编译器,我们建议使用SDKMAN!。从 sdkman.io 获取SDKMAN!并使用以下命令安装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上,按照 这些说明 安装GraalVM或 22.3 版本的 Liberica Native Image Kit、Visual Studio Build Tools和Windows SDK。由于 Windows相关命令行的最大长度,请确保使用x64 Native Tools Command Prompt,而不是普通的Windows命令行来运行Maven或Gradle插件。
2.3.2. 使用 Maven
与 buildpack 支持一样,你需要确保使用 spring-boot-starter-parent
来继承 native
配置文件(profile),并确保使用 org.graalvm.buildtools:native-maven-plugin
插件。
在 native
配置文件激活时,你可以调用 native:compile
goal 来触发 native-image
编译。
$ mvn -Pnative native:compile
在 target
目录中可以找到原生镜像的可执行文件。
2.3.3. 使用 Gradle
当Native Build Tools Gradle插件应用于你的项目时,Spring Boot Gradle插件将自动触发Spring AOT引擎。任务的依赖是自动配置的,所以你只需运行标准的 nativeCompile
任务就可以生成一个原生镜像。
$ gradle nativeCompile
可以在 build/native/nativeCompile
目录下找到原生镜像的可执行文件。
2.3.4. 运行示例
此时,你的应用程序应该可以工作了,你现在可以通过直接运行它来启动应用程序。
$ target/myproject
$ build/native/nativeCompile/myproject
你应该看到类似以下的输出。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0-SNAPSHOT)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
启动时间因机器而异,但应该比运行在JVM上的Spring Boot应用快得多。 |
如果你打开web浏览器,访问 localhost:8080
,你应该看到以下输出。
Hello World!
要优雅地退出应用程序,按 ctrl-c
。
3. 测试 GraalVM 原生镜像
在编写原生镜像应用程序时,我们建议你继续尽可能地使用JVM来开发大部分的单元和集成测试。 这将有助于减少开发人员的构建时间,并允许你使用现有的IDE集成。 在JVM上有了广泛的测试覆盖,你就可以把原生镜像测试的重点放在可能有差异的地方。
对于原生镜像测试,你通常要确保以下几个方面的工作。
-
Spring AOT引擎能够处理你的应用程序,它将以AOT处理的模式运行。
-
GraalVM有足够的hint,以确保可以产生有效的原生镜像。
3.1. 用JVM测试提前处理(Ahead-of-time Processing)
当Spring Boot应用程序运行时,它会尝试检测它是否以原生镜像的形式运行。如果是以原生镜像运行,它将使用Spring AOT引擎在构建时生成的代码来初始化应用程序。
如果应用程序是在普通的JVM上运行,那么任何AOT生成的代码都会被忽略。
由于 native-image
编译阶段可能需要一段时间才能完成,有时在JVM上运行你的应用程序,但让它使用AOT生成的初始化代码是很有用的。这样做可以帮助你快速验证AOT生成的代码中是否有错误,当你的应用程序最终被转换为原生镜像时,没有任何东西被遗漏。
要在JVM上运行Spring Boot应用程序并让它使用AOT生成的代码,你可以将 spring.aot.enabled
系统属性设置为 true
。
例如:
$ java -Dspring.aot.enabled=true -jar myapplication.jar
你需要确保你测试的jar包含AOT生成的代码。对于Maven来说,这意味着你应该用 -Pnative 构建,以激活 native 配置文件。对于Gradle,你需要确保你的构建包括 org.graalvm.buildtools.native 插件。
|
如果你的应用程序开始时将 spring.aot.enabled
属性设置为 true
,那么你就有更大的把握在转换为原生镜像时能够工作。
你也可以考虑针对运行中的应用程序运行集成测试。例如,你可以使用Spring WebClient
来调用你的应用程序REST端点。或者你可以考虑使用Selenium这样的项目来检查你的应用程序的HTML响应。
3.2. 使用 Native Build Tools 进行测试
GraalVM Native Build Tools 包括在本地镜像中运行测试的功能。当你想深入测试你的应用程序的内部结构在GraalVM本地镜像中的工作情况时,这可能会很有帮助。
生成包含要运行的测试的本地映像可能是一个耗时的操作,所以大多数开发人员可能更愿意在本地使用JVM。然而,作为CI管道的一部分,它们可能非常有用。例如,你可以选择每天运行一次本地测试。
Spring框架包括对运行测试的提前支持。所有常见的Spring测试功能都可以与原生镜像测试一起使用。例如,你可以继续使用 @SpringBootTest
注解。你还可以使用Spring Boot test slices,只测试你的应用程序的特定部分。
Spring Framework的原生测试支持以如下方式工作。
-
对测试进行分析,以发现任何需要的
ApplicationContext
实例。 -
对这些应用上下文进行预先处理,并生成资源。
-
创建一个原生镜像,生成的资源由GraalVM处理。
-
原生镜像还包括JUnit
TestEngine
,配置了一个已发现的测试列表。 -
原生镜像被启动,触发引擎,运行每个测试并报告结果。
3.2.1. 使用 Maven
要使用Maven运行本地测试,确保你的 pom.xml
文件使用 spring-boot-starter-parent
。你应该有一个 <parent>
部分,看起来像这样。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>
spring-boot-starter-parent
声明了一个 nativeTest
配置文件,配置了运行本地测试所需的executions。你可以使用命令行上的 -P
标志激活配置文件。
如果你不想使用 spring-boot-starter-parent ,你就需要为Spring Boot插件的 process-test-aot goal 和 Native Build Tools 插件的 test goal 配置 executions。
|
为了建立镜像和运行测试,使用 nativeTest
配置文件激活的 test
goal。
$ mvn -PnativeTest test
4. 原生镜像的高级主题
4.1. 嵌套的配置特性(Configuration Properties)
反射提示(Reflection hint)是由Spring的超前引擎(ahead-of-time engine)为配置属性自动创建的。然而,不属于内部类的嵌套配置属性必须用 @NestedConfigurationProperty
来注解,否则它们将不会被检测到,也不能被绑定。
@ConfigurationProperties(prefix = "my.properties")
public class MyProperties {
private String name;
@NestedConfigurationProperty
private final Nested nested = new Nested();
}
其中 Nested
(嵌套的) 是:
public class Nested {
private int number;
}
上面的例子为 my.properties.name
和 my.properties.nested.number
产生配置属性。如果没有嵌套字段上的 @NestedConfigurationProperty
注解,my.properties.nested.number
属性将不能在原生镜像中被绑定。
当使用构造函数绑定时,你必须用 @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;
}
}
当使用 record 时,你必须用 @NestedConfigurationProperty
来注解这个参数。
@ConfigurationProperties(prefix = "my.properties")
public record MyPropertiesRecord(String name, @NestedConfigurationProperty Nested nested) {
}
当使用 Kotlin 时,你需要用 @NestedConfigurationProperty
来注解数据类(data class)的参数。
@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生成的资源,就可以将该jar转换为原生镜像。这在很多方面都很有用,包括。
-
你可以保留你的常规JVM pipeline,并在你的CI/CD平台上将JVM应用程序变成一个原生镜像。
-
由于
native-image
不支持交叉编译,你可以保留一个操作系统中立的部署工件,以后转换为不同的操作系统架构。
你可以使用 Cloud Native Buildpacks 将 Spring Boot 的可执行jar转换为原生镜像,或者使用GeralVM附带的 native-image
工具。
你的可执行jar必须包括AOT生成的资源,如生成的类和JSON hint文件。 |
4.2.1. 使用 Buildpacks
Spring Boot 应用程序通常通过Maven(mvn spring-boot:build-image
)或Gradle(gradle bootBuildImage
)集成使用 Cloud Native Buildpacks。
不过,你也可以使用 pack
,把经过AOT处理的Spring Boot可执行jar变成一个原生容器镜像。
首先,确保有一个Docker守护程序(详情见 Get Docker)。如果你是在Linux上,将其 配置为允许非root用户。
你还需要按照 buildpacks.io上的安装指南 来安装 pack
。
假设目标目录下有一个经过AOT处理的Spring Boot可执行jar,构建为 myproject-0.0.1-SNAPSHOT.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
你不需要有一个本地的GraalVM安装,就可以通过这种方式生成一个镜像。 |
一旦 pack
完成,你就可以用 docker run
启动应用程序。
$ 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 native-image
工具。要做到这一点,你需要在你的机器上有一个 GraalVM 发行版。你可以在 Liberica Native Image Kit 页面 上手动下载,也可以使用SDKMAN这样的下载管理器!。
假设目标目录下有一个经过AOT处理的Spring Boot可执行jar,构建为 myproject-0.0.1-SNAPSHOT.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 ../
这些命令可以在Linux或macOS机器上使用,但你需要对它们进行调整以适应Windows。 |
@META-INF/native-image/argfile 可能没有被打包到你的jar中。只有在需要覆盖可及性元数据(reachability metadata)时才会包含它。
|
native-image -cp 标志不接受通配符。你需要确保所有的jar都被列出(上面的命令使用 find 和 tr 来做这个)。
|
4.3. 使用追踪代理(Tracing Agent)
GraalVM 原生镜像 tracing agent 允许你在JVM上拦截反射、资源或代理的使用,以便生成相关的hint。Spring 应该会自动生成大部分的hint,但 tracing agent 可以用来快速识别缺少的条目。
当使用代理生成原生镜像的hint时,有几种方法。
-
直接启动应用程序并“行使”它。
-
运行应用测试以“行使”应用程序。
当一个库或一个模式不被Spring识别时,第一个选项对于识别缺失的hint很有意思。
对于可重复的设置来说,第二个选项听起来更有吸引力,但默认情况下,生成的hint将包括测试基础设施所需的任何东西。当应用程序真正运行时,其中一些将是不必要的。为了解决这个问题,代理支持一个访问过滤文件,它将导致某些数据从生成的输出中排除。
4.3.1. 直接启动应用程序
使用以下命令来启动附有原生镜像跟踪代理(tracing agent)的应用程序。
$ java -Dspring.aot.enabled=true \
-agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
-jar target/myproject-0.0.1-SNAPSHOT.jar
现在你可以“行使”你希望有hint的代码路径,然后用 ctrl-c
停止应用程序。
在应用程序关闭时,原生镜像追踪代理(tracing agent)将把hint文件写入给定的配置输出目录。你可以手动检查这些文件,或者将它们作为原生镜像构建过程的输入。要使用它们作为输入,请将它们复制到 src/main/resources/META-INF/native-image/
目录中。下次你构建原生镜像时,GraalVM 将考虑这些文件。
有更多的高级选项可以在原生镜像追踪代理(tracing agent)上设置,例如按调用者类别过滤记录的hint,等等。进一步阅读,请看 官方文档。
4.4. 自定义Hint
如果你需要为反射、资源、序列化、代理使用等提供自己的hint,你可以使用 RuntimeHintsRegistrar
API。创建一个实现 RuntimeHintsRegistrar
接口的类,然后对提供的 RuntimeHints
实例进行适当调用。
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
注解的application类)上使用 @ImportRuntimeHints
来激活这些hint。
如果你有需要进行绑定的类(大部分在序列化或反序列化JSON时需要),你可以在任何Bean上使用 @RegisterReflectionForBinding
。大多数hint都是自动推断的,例如在接受或从 @RestController
方法返回数据时。但是当你直接使用 WebClient
或 RestTemplate
工作时,你可能需要使用 @RegisterReflectionForBinding
。
4.4.1. 测试自定义Hint
RuntimeHintsPredicates
API可以用来测试你的hint。该API提供了建立 Predicate
的方法,可以用来测试一个 RuntimeHints
实例。
如果你使用AssertJ,你的测试会像这样。
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社区正在通过为尚未发布自己的项目提供 可达性元数据(reachability metadata) 来提供帮助。Spring本身并不包含对第三方库的hint,而是依赖于可达性元数据项目。
如果你在为Spring Boot应用程序生成本地镜像时遇到问题,请查看 Spring Boot wiki 的 Spring Boot with GraalVM 页面。你也可以向GitHub上的 spring-aot-smoke-tests 项目提交问题,该项目用于确认常见的应用程序类型是否按预期运行。
如果你发现某个库不能与GraalVM一起使用,请在 可及性元数据(reachability metadata)项目 中提出issue。
5. 接下来读什么
如果你想进一步了解我们的构建插件所提供的提前处理,请参阅 Maven 和 Gradle 插件文档。要了解更多用于执行处理的API,请浏览Spring框架源代码中的 org.springframework.aot.generate
和 org.springframework.beans.factory.aot
包。
关于 Spring 和 GraalVM 的已知限制,请参见 Spring Boot wiki。
下一节将继续介绍 Spring Boot CLI。