当前位置:   article > 正文

GraalVM Native Image学习记笔_graalvm native images

graalvm native images

GraalVM Native Image学习记笔


目录

1、GraalVM Native Images介绍

1.1 GraalVM Native Images简介

1.2 和JVM部署的主要区别

1.3 了解Spring AOT原理

1.3.1源代码生成

1.3.2. 提示文件生成

1.3.3. 代理类生成

2、开始你的第一个GraalVM Native 应用

2.1 应用示例

2.2 Buildpacks构建

2.2.1 环境要求

2.2.2 Maven构建

2.2.3 Gradle构建

2.2.4 Run Example

2.3 Native Build Tools构建

2.3.1 环境要求

2.3.2 Maven构建

2.3.3 Gradle构建

2.3.4 Run Example

3、测试 GraalVM Native Images

3.1 使用JVM进行AOT测试

3.2 使用Native Build Tools(本机构建工具)进行测试

3.2.1 Using Maven

4、 Native Images 高级应用

4.1嵌套的properties配置

4.2 将jar转为一个可执行的本地iamge

4.2.1 使用Buildpacks

4.2.2 使用GraalVM native-image

4.3 使用Tracing Agent

4.3.1. 直接启动应用程序

4.4 自定义Hint

4.4.1. 测试自定义Hint

 4.5 已知的限制

5、接下来要读什么


1、GraalVM Native Images介绍

1.1 GraalVM Native Images简介

  • GraalVM提供一种全新的方式让你发布及运行你的java应用,与传统的JVM相比,具备更小的内存占用合更快的启动速度。
  • 非常适合用容器来部署应用,适合Function as a service(FaaS)应用场景。
  • GrralVM使用AOT技术,在程序运行前将源代码编译为二进制的可执行的静态文件,并确定应用程序入口。
  • GraalVM本机映像是一个完整的、特定于平台的可执行文件。不需要为了运行本机映像而提供Java虚拟机。

1.2 和JVM部署的主要区别

  1. 应用程序的静态分析是在构建时从主入口点执行的。
  2. 创建本机映像时无法访问的代码将被删除,并且不会成为可执行文件的一部分。
  3. GraalVM不直接知道代码中的动态元素,必须了解反射、资源、序列化和动态代理。
  4. 应用程序类路径在构建时是固定的,不能更改。
  5. 没有延迟类加载,可执行文件中提供的所有内容都将在启动时加载到内存中。
  6. Java应用程序的某些方面存在一些不完全支持的限制。

GraalVM参考文档的Native Image Compatibility Guide部分提供了有关GraalVM限制的更多详细信息。

Native Image Compatibility Guide

1.3 了解Spring AOT原理

典型的Spring Boot应用程序是动态的,配置是在运行时执行的。事实上,Spring Boot自动配置的概念在很大程度上取决于对运行时状态的反应,以便正确配置。尽管可以告诉GraalVM应用程序的这些动态方面,但这样做会抵消静态分析的大部分好处。因此,当使用Spring Boot创建本机映像时,假设是一个封闭的世界,并且应用程序的动态方面受到限制。

SpringBoot本地编译限制:

  1. 类路径是固定的,并在构建时完全定义
  2. 应用程序中定义的bean在运行时无法更改,这意味着:

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

不支持创建bean时更改的Properties(例如@ConditionalOnProperty和.enable Properties)

当这些限制是明确的,GraalV在构建Spring时提前处理, 通过AOT处理通常会生成:

  1. java源代码
  2. 字节码(用于动态代理等)
  3. GraalVM JSON提示文件:
    1. 资源提示(Resource-config.json)
    2. 反射提示(reflect-config.json)
    3. 序列化提示(Serialization-config.json)
    4. Java代理提示(Proxy-config.json)
    5. JNI提示(JNI-config.json)

1.3.1源代码生成

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

假设我们定义一个 @Configuration注解类,并在下面的代码中使用了它:

  1. @Configuration(proxyBeanMethods = false)
  2. public class MyConfiguration
  3. {
  4. @Bean public MyBean myBean() {
  5. return new MyBean();
  6. }
  7. }

bean定义是通过解析@Configuration类并找到@bean方法来创建的。在上面的例子中,我们为一个名为myBean的单例bean定义了BeanDefinition。我们还在为MyConfiguration类本身创建一个BeanDefinition。

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

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

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

  1. import org.springframework.beans.factory.aot.BeanInstanceSupplier;
  2. import org.springframework.beans.factory.config.BeanDefinition;
  3. import org.springframework.beans.factory.support.RootBeanDefinition;
  4. /**
  5. * Bean definitions for {@link MyConfiguration}.
  6. */
  7. public class MyConfiguration__BeanDefinitions {
  8. /**
  9. * Get the bean definition for 'myConfiguration'.
  10. */
  11. public static BeanDefinition getMyConfigurationBeanDefinition() {
  12. Class<?> beanType = MyConfiguration.class;
  13. RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
  14. beanDefinition.setInstanceSupplier(MyConfiguration::new);
  15. return beanDefinition;
  16. }
  17. /**
  18. * Get the bean instance supplier for 'myBean'.
  19. */
  20. private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
  21. return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean")
  22. .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
  23. }
  24. /**
  25. * Get the bean definition for 'myBean'.
  26. */
  27. public static BeanDefinition getMyBeanBeanDefinition() {
  28. Class<?> beanType = MyBean.class;
  29. RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
  30. beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
  31. return beanDefinition;
  32. }
  33. }

Note1:

据bean定义的性质,生成的确切代码可能会有所不同。

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

myConfigurationbean有一个bean定义,myBean也有一个定义。当需要myBean实例时,会调用BeanInstanceSupplier。此供应商将调用myConfigurationbean上的myBean()方法。

Note2:

在Spring AOT处理过程中,应用程序会启动,并会指向那些可用的ben。Bean实例不是在AOT处理阶段创建的。

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

尽管AOT生成的源代码可能很冗长,但它可读性很强,在调试应用程序时也很有帮助。当使用Maven和带有Gradle的build/regenerated/aotSources时,可以在target/spring aot/main/sources中找到生成的源文件。

1.3.2. 提示文件生成

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

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

提示文件是在META-INF/本地映像下生成的,GraalVM会自动拾取这些文件。

当使用Maven和带有Gradle的build/regenerated/aotResources时,可以在target/spring aot/main/resources中找到生成的提示文件。

1.3.3. 代理类生成

Spring有时需要生成代理类来增强编写的具有附加功能的代码。为此,它使用了cglib库,该库直接生成字节码。

当应用程序在JVM上运行时,代理类会随着应用程序的运行而动态生成。创建本机映像时,需要在构建时创建这些代理,以便GraalVM可以包含这些代理。

与源代码生成不同,生成的字节码在调试应用程序时并没有特别的帮助。然而,如果需要使用javap等工具检查.class文件的内容,可以在Maven的target/spring aot/main/classes和Gradle的build/regenerated/aotClasses中找到它们。

2、开始你的第一个GraalVM Native 应用

现在我们已经很好地概述了GraalVM Native Images以及Spring提前引擎的工作原理,我们可以看看如何创建应用程序。

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

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

Note1:

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

2.1 应用示例

我们需要一个可以用来创建本地映像的示例应用程序。就我们的目的而言,“入门.html”部分介绍的简单的“Hello World!”web应用程序就足够了。

概括一下,我们的主要应用程序代码如下所:

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @SpringBootApplication
  7. public class MyApplication {
  8. @RequestMapping("/")
  9. String home() {
  10. return "Hello World!";
  11. }
  12. public static void main(String[] args) {
  13. SpringApplication.run(MyApplication.class, args);
  14. }
  15. }

 该应用程序使用Spring MVC和嵌入式Tomcat,这两种应用程序都经过测试和验证,可以使用GraalVM本机映像。

2.2 Buildpacks构建

Spring Boot支持对Maven和Gradle的本地imaeg的直接构建。这意味着只需键入一个命令,就可以在本地运行的Docker守护进程中快速获得一个合理的image。生成的image不包含JVM,而是静态编译本机image。这会导致image变小。

Note1:

用于image的构建器是:paketobuildpacks/builder:tiny。它具有体积小,轻量级特点,但如果需要,也可以使用paketobuildpacks/builder:base或paketobbuildpacks/builder:full在imags中提供更多工具。

2.2.1 环境要求

应该安装docker。有关更多详细信息,请参阅Get-Docker。如果在Linux上,请将其配置为允许非root用户。

Note1:

可以运行docker-run-helloworld(不带sudo)来检查docker守护进程是否可以按预期访问。查看Maven或Gradle Spring Boot插件文档以了解更多详细信息。

在macOS上,建议将分配给Docker的内存增加到至少8GB,并可能增加更多CPU。有关更多详细信息,请参阅此堆栈溢出答案。在Microsoft Windows上,请确保启用Docker WSL 2后端以获得更好的性能。

2.2.2 Maven构建

要使用Maven构建本地映像容器,应该确保pom.xml文件使用spring-boot-starter父文件和org.graalvm.buildtools:native Maven插件。应该有一个部分,如下所示:

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>3.0.5</version>
  5. </parent>

 此外,应该在<build><plugins>部分中有这样的内容:

  1. <plugin>
  2. <groupId>org.graalvm.buildtools</groupId>
  3. <artifactId>native-maven-plugin</artifactId>
  4. </plugin>

 spring-boot-starter父级声明一个本机配置文件,该配置文件配置创建本机映像所需运行的执行。你可以使用命令行上的-P标志激活配置文件。

如果你不想使用spring-boot starter父级,你需要从spring-boot的插件中为进程aot目标和从Native Build Tools插件中为添加可达性元数据目标配置执行。

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

$ mvn -Pnative spring-boot:build-image

2.2.3 Gradle构建

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

只要应用了org.graalvm.buildtools.native插件,bootBuildImage任务就会生成一个本地映像,而不是JVM映像。可以使用以下方法运行任务:

$ gradle bootBuildImage

2.2.4 Run Example

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

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

___ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.5) ....... . . . ....... . . . (log output here) ....... . . . ........ Started MyApplication in 0.08 seconds (process running for 0.095)

启动时间因机器而异,但应该比JVM上运行的Spring Boot应用程序快得多。

如果打开一个web浏览器到localhost:8080,应该会看到以下输出:

你好,世界!

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

2.3 Native Build Tools构建

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

2.3.1 环境要求

  • Linux and macOS

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

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

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

  1. $ java -version
  2. openjdk version "17.0.5" 2022-10-18
  3. LTS OpenJDK Runtime Environment
  4. GraalVM 22.3.0 (build 17.0.5+8-LTS)
  5. OpenJDK 64-Bit Server
  6. 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 Maven构建

与buildpack支持一样,需要确保使用spring-boot-starter-parent来继承本机profile文件,并且使用org.graalvm.buildtools:native-maven插件。

在激活本机profile文件的情况下,可以调用native:compile目标来触发本机映像编译:

$ mvn -Pnative native:compile

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

2.3.3 Gradle构建

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

$ gradle nativeCompile

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

2.3.4 Run Example

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

Maven

. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.5) ....... . . . ....... . . . (log output here) ....... . . . ........ Started MyApplication in 0.08 seconds (process running for 0.095)

启动时间因机器而异,但应该比JVM上运行的Spring Boot应用程序快得多。

如果打开一个web浏览器到localhost:8080,应该会看到以下输出:

你好,世界!

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

3、测试 GraalVM Native Images

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

对于本机images测试,通常希望确保以下方面有效:

  • Spring AOT引擎能够处理的应用程序,并且它将以AOT处理模式运行。
  • GraalVM有足够的提示来确保可以生成有效的本机images

3.1 使用JVM进行AOT测试

当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

注意:

需要确保正在测试的jar包含AOT生成的代码。对于Maven来说,这意味着应该使用-Pnative进行构建以激活本机概要文件。对于Gradle,需要确保的构建包含org.graalvm.buildtools.native插件。

如果你的应用程序在启动时将spring.aot.enabled属性设置为true,那么你就更有信心在转换为本机映像时它会工作。

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

3.2 使用Native Build Tools(本机构建工具)进行测试

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

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

Spring Framework包括对运行测试的AOT支持。所有常见的Spring测试功能都可以使用本机iamges测试。例如,你可以继续使用@SpringBootTest注释。你也可以使用 Spring Boot test slices来只测试应用程序的特定部分。

Spring Framework的本机测试支持以以下方式工作:

  • 对测试进行分析,以发现所需的任何ApplicationContext实例。
  • 将提前处理应用于这些应用程序上下文中的每一个,并生成资产。
  • 创建一个本地images,生成的资源由GraalVM处理。
  • 本机images还包括JUnit测试引擎,该引擎配置有已发现测试的列表。
  • 启动本机images,触发引擎,该引擎将运行每个测试并报告结果。

3.2.1 Using Maven

要使用Maven运行本机测试,请确保pom.xml文件使用spring-boot-starter父级,你应该有一个部分,如下所示:

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>3.0.5</version>
  5. </parent>

spring-boot-starter父级声明了一个nativeTest配置文件,该配置文件配置运行本机测试所需的执行。你可以使用命令行上的-P标志激活配置文件。

提醒:

如果你不想使用spring-boot-starter-parent,你需要从spring-boot插件中为process-test-aot和Native Build Tools插件中为测试目标配置执行。

要构建images并运行测试,请在nativeTest配置文件处于活动状态的情况下使用测试目标:

$ gradle nativeTest

4、 Native Images 高级应用

4.1嵌套的properties配置

Spring AOT引擎会自动创建反射配置文件。但是,非内部类的嵌套配置properties必须用@NestedConfigurationProperty进行注释,否则将无法检测到它们,也无法绑定。

 

  1. import org.springframework.boot.context.properties.ConfigurationProperties;
  2. import org.springframework.boot.context.properties.NestedConfigurationProperty;
  3. @ConfigurationProperties(prefix = "my.properties")
  4. public class MyProperties {
  5. private String name;
  6. @NestedConfigurationProperty
  7. private final Nested nested = new Nested();
  8. public String getName() {
  9. return this.name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public Nested getNested() {
  15. return this.nested;
  16. }
  17. }

其中Nested是: 

  1. public class Nested {
  2. private int number;
  3. public int getNumber() {
  4. return this.number;
  5. }
  6. public void setNumber(int number) {
  7. this.number = number;
  8. }
  9. }

上面的示例为my.properties.name和my.proporties.nested.number生成配置财产。如果嵌套字段上没有 @NestedConfigurationProperty 注释,my.propropertiex.nested_number 属性将无法在本机映像中绑定。

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

  1. import org.springframework.boot.context.properties.ConfigurationProperties;
  2. import org.springframework.boot.context.properties.NestedConfigurationProperty;
  3. @ConfigurationProperties(prefix = "my.properties")
  4. public class MyPropertiesCtor {
  5. private final String name;
  6. @NestedConfigurationProperty
  7. private final Nested nested;
  8. public MyPropertiesCtor(String name, Nested nested) {
  9. this.name = name;
  10. this.nested = nested;
  11. }
  12. public String getName() {
  13. return this.name;
  14. }
  15. public Nested getNested() {
  16. return this.nested;
  17. }
  18. }

使用记录时,必须使用 @NestedConfigurationProperty 对参数进行注释:

  1. import org.springframework.boot.context.properties.ConfigurationProperties;
  2. import org.springframework.boot.context.properties.NestedConfigurationProperty;
  3. @ConfigurationProperties(prefix = "my.properties")
  4. public record MyPropertiesRecord(String name, @NestedConfigurationProperty Nested nested) {
  5. }

 使用Kotlin时,需要使用@NestedConfigurationProperty注释数据类的参数: 

  1. import org.springframework.boot.context.properties.ConfigurationProperties
  2. import org.springframework.boot.context.properties.NestedConfigurationProperty
  3. @ConfigurationProperties(prefix = "my.properties")
  4. data class MyPropertiesKotlin(
  5. val name: String,
  6. @NestedConfigurationProperty val nested: Nested
  7. )

注意:

请在所有情况下使用public getter和setter,否则proporties将不可绑定。

4.2 将jar转为一个可执行的本地iamge

只要jar包含AOT生成的资产,就可以将Spring Boot可执行jar转换为本地iamge。这可能是有用的,原因有很多,包括:

  • 你可以保留常规的JVM管道,并将JVM应用程序转换为CI/CD平台上的本机iamge。
  • 由于本机iamge不支持交叉编译,你可以保留一个与操作系统无关的部署工件,稍后将其转换为不同的操作系统体系结构。

你可以使用Cloud native Buildpacks或GraalVM附带的本地iamge工具将Spring Boot可执行jar转换为本地映iamge。

注意:

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

4.2.1 使用Buildpacks

SpringBoot应用程序通常通过Maven(mvn-Spring-Boot:build-image)或Gradle(Gradle-bootBuildImage)集成使用云原生构建包。你也可以使用pack将经过AOT处理的Spring Boot可执行jar转换为本地 container image.。

首先,确保有一个Docker守护进程可用(有关更多详细信息,请参阅Get-Docker)。如果你在Linux上,请将其配置为允许非root用户。

你还需要按照buildpacks.io上的安装指南安装pack。

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

 

  1. $ pack build --builder paketobuildpacks/builder:tiny \
  2. --path target/myproject-0.0.1-SNAPSHOT.jar \
  3. --env 'BP_NATIVE_IMAGE=true' \
  4. my-application:0.0.1-SNAPSHOT

注意:

你不需要安装本地GraalVM就可以以这种方式生成映像。

打包完成后,你可以使用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本机映像工具。为了实现这一点,你需要在机器上安装GraalVM分发版。你可以在Liberica Native Image Kit页面上手动下载,也可以使用SDKMAN这样的下载管理器。

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

  1. $ rm -rf target/native
  2. $ mkdir -p target/native
  3. $ cd target/native
  4. $ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar
  5. $ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'`
  6. $ mv myproject ../

 注意:

上面命令适用于Linux或macOS机器,如果要在Windows运行,需要自己适应。

@META-INF/nature image/argfile可能没有打包在jar中。只有当需要覆盖可达性元数据时,才会包含它。

警告:

nativ-image -cp标志不接受通配符。你需要确保列出了所有的jar(上面的命令使用find和tr来执行此操作)。

4.3 使用Tracing Agent

GraalVM native-image Tracing Agent允许你截获JVM上的反射、资源或代理使用情况,以便生成相关提示。Spring应该自动生成大多数提示,但可以使用Tracing Agent快速识别丢失的条目。

当使用代理为本地映像生成提示时,有几种方法:

  • 直接启动应用程序并进行练习。
  • 运行应用程序测试来练习应用程序。

当Spring无法识别库或行为时,第一个选项有助于识别丢失的提示。

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

4.3.1. 直接启动应用程序

使用以下命令启动连接了本机图像跟踪代理的应用程序:

  1. $ java -Dspring.aot.enabled=true \
  2. -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
  3. -jar target/myproject-0.0.1-SNAPSHOT.jar

 现在,你可以练习想要提示的代码路径,然后使用ctrl-c停止应用程序。

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

可以在native iamge tracing agent 代理上设置更高级的选项,例如通过调用程序类过滤录制的提示等。有关进一步的阅读,请参阅官方文档。

4.4 自定义Hint

如果你需要为反射、资源、序列化、代理使用等提供自己的提示,你可以使用RuntimeHintsRegisterAPI。创建一个实现RuntimeHintsRegistrar接口的类,然后对提供的RuntimeHints实例进行适当的调用:

  1. import java.lang.reflect.Method;
  2. import org.springframework.aot.hint.ExecutableMode;
  3. import org.springframework.aot.hint.RuntimeHints;
  4. import org.springframework.aot.hint.RuntimeHintsRegistrar;
  5. import org.springframework.util.ReflectionUtils;
  6. public class MyRuntimeHints implements RuntimeHintsRegistrar {
  7. @Override
  8. public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
  9. // Register method for reflection
  10. Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
  11. hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
  12. // Register resources
  13. hints.resources().registerPattern("my-resource.txt");
  14. // Register serialization
  15. hints.serialization().registerType(MySerializableClass.class);
  16. // Register proxy
  17. hints.proxies().registerJdkProxy(MyInterface.class);
  18. }
  19. }

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

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

4.4.1. 测试自定义Hint

RuntimeHintsPredicates API可用于测试提示。API提供了构建测试的方法,该方法可用于测试RuntimeHints实例。

如果你使用的是AssertJ,那么你的测试将如下所示:

  1. import org.junit.jupiter.api.Test;
  2. import org.springframework.aot.hint.RuntimeHints;
  3. import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
  4. import org.springframework.boot.docs.nativeimage.advanced.customhints.MyRuntimeHints;
  5. import static org.assertj.core.api.Assertions.assertThat;
  6. class MyRuntimeHintsTests {
  7. @Test
  8. void shouldRegisterHints() {
  9. RuntimeHints hints = new RuntimeHints();
  10. new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());
  11. assertThat(RuntimeHintsPredicates.resource().forResource("my-resource.txt")).accepts(hints);
  12. }
  13. }

 4.5 已知的限制

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

如果你在为Spring Boot应用程序生成native image 时遇到问题,请查看Spring Boot wiki的Spring Boot with GraalVM页面。你还可以向GitHub上的spring-aot-smoke-tests项目提交问题,该项目用于确认常见的应用程序类型是否按预期工作。

如果您发现一个库不适用于GraalVM,请提出可达性元数据项目的问题。

5、接下来要读什么

如果你想了解更多关于我们的构建插件提供的提前处理的信息,请参阅Maven和Gradle插件文档。要了解有关用于执行处理的API的更多信息,请浏览Spring Framework源代码的org.springframework.aot.generate和org.springfframework.beans.factory.aot包。

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/68638
推荐阅读
相关标签
  

闽ICP备14008679号