Spring Boot应用使用GraalVM本地编译相关配置

1. 介绍

Java应用程序可以通过Graalvm Native Image提前编译生成与本地机器相关的可执行文件。与在JVM执行java程序相比,Native Image占用内存更小和启动速度更快。

从spring boot3开始支持GraalVM Native Image,因此要使用此特性,需要把spring boot升级到3.0.0以上, 其中,JDK也要升级到17以上。

官方提供的示例见Developing Your First GraalVM Native Application。示例代码与普通spring boot一样,可以忽略,继续看下面内容。

1.1 maven插件配置

示例中与spring boot项目一样,不同之处在于打包配置,比如maven pom.xml文件中需要加上graalvm的native-maven-plugin插件,如下:

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

<!--省略-->

<dependencies>
<!--省略-->
</dependencies>

<build>
 <plugins>
   <plugin>
     <groupId>org.graalvm.buildtools</groupId>
     <artifactId>native-maven-plugin</artifactId>
   </plugin>
   <plugin>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
    <!--其它插件-->
 </plugins>
</build>

pom说明:<parent>项指定为spring-boot-starter-parent,这样你就可以减少许多配置,就像上面一样。
spring-boot-starter-parent中为生成Native Image定义了名为native的profile,打包生成可执行文件使用如下maven命令

mvn clean package -Pnative

1.2 maven插件完整配置

如果pom的<parent>项不是spring-boot-starter-parent,你就需要像如下配置

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
   <version>3.3.0</version>
  <configuration>
    <archive>
      <manifestEntries>
        <Spring-Boot-Native-Processed>true</Spring-Boot-Native-Processed>
      </manifestEntries>
    </archive>
  </configuration>
</plugin>
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>3.3.0</version>
  <configuration>
    <image>
      <builder>paketobuildpacks/builder-jammy-tiny:latest</builder>
      <env>
        <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
      </env>
    </image>
  </configuration>
  <executions>
    <execution>
      <id>process-aot</id>
      <goals>
        <goal>process-aot</goal>
      </goals>
    </execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <version>0.10.2</version>
  <configuration>
    <classesDirectory>${project.build.outputDirectory}</classesDirectory>
  </configuration>
  <executions>
    <execution>
      <id>add-reachability-metadata</id>
      <goals>
        <goal>add-reachability-metadata</goal>
      </goals>
    </execution>
  </executions>
</plugin>

2. 开发注意事项

Spring Boot对自身提供的bean自动注入、AOP配置、factories等特性做了Native支持,在静态编译期间相关类都可达,
但我们的项目中还有些不受spring AOT支持的代码,比如业务代码中的反射、JSON和对象转换、以及三方jar包中动态代码等。
我们需要手动配置提供hint,graalvm才能把这些动态代码编译到可执行文件中去,否则执行时会抛出异常。

2.1 项目中动态代码支持

提供动态代码的hint配置,有三种方式:

  1. 第一种遵从graalvm规范提供相应的json配置,这个可以见往期分享Graalvm配置文件与Feature和Substitute机制介绍
  2. 第二种扩展Spring接口,即实现RuntimeHintsRegistrar接口,通过代码指定哪些类、资源需要hint。
  3. 第三种反射类可以使用@RegisterReflectionForBinding由spring自动绑定反射配置。

下面介绍后面两种方式

2.1.1 扩展Spring接口RuntimeHintsRegistrar

  1. 示例代码如下:
    public class MyAOTRuntimeHints 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);
          // 反射注册
          hints.reflection().registerType(UserModel.class, typeHint -> {
             typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
             MemberCategory.INVOKE_DECLARED_METHODS,
             MemberCategory.DECLARED_FIELDS);
          });
          // 资源文件注册
          hints.resources().registerPattern("resource_hint.properties");
          
         // Serialization类注册
         hints.serialization().registerType(Email.class);
          
         // 代理注册
         // hints.proxies().registerJdkProxy(MyInterface.class);
       }
    }
    
  2. 注册Hint类。MyAOTRuntimeHints还需要使用@ImportRuntimeHints注解到任何@Configuration class,或者在META-INF/aot.factories文件注册
    1. 使用@ImportRuntimeHints
      @Configuration
      @ImportRuntimeHints(value = {MyAOTRuntimeHints.class})
      public class AOTConfiguration {}
      
    2. 注册META-INF/aot.factories
      org.springframework.aot.hint.RuntimeHintsRegistrar=com.example.springnative.aot.hit.MyAOTRuntimeHints
      

2.1.2 使用注解@RegisterReflectionForBinding

对于项目中使用反射的地方,可以直接通过注解来配置hint,把@RegisterReflectionForBinding注解到任何@Configuration class类上。
示例如下:

@Configuration
@RegisterReflectionForBinding(classes = {MyJSONBean.class})
public class AOTConfiguration {}

2.2 代码中隐式使用反射的地方

2.2.1 JSON

json工具推荐使用jackson,spring中也对其做了支持。
把对象转为json或把json转为对象,对象中所有类都要注册反射hint。
如UserModel在在reflect-config.json配置如下

{
"name": "com.example.springnative.model.UserModel",
"allPublicConstructors": true,
"allDeclaredFields": true,
"allPublicMethods": true
}

如果javabean实现了java.io.Serializable接口,可以在serialization-config.json中注册,示例如下。

{
"name": "com.example.springnative.model.UserModel"
}

2.3 Configuration Properties类嵌套

如果Properties类嵌套了其它类型,则必须要使用@NestedConfigurationProperty注解,否则spring AOT没法识别到这个嵌套类,就不能为其注册反射hint。如下示例。

@ConfigurationProperties(prefix = "my.properties")
public class MyProperties {
    @NestedConfigurationProperty
    private final Nested nested = new Nested();
    // getters / setters...
}

public class Nested {
   private int number;
   // getters / setters...

}

2.4 Native Image执行与JVM执行区别

  1. 应用程序类路径在生成时是固定的,不能更改。
  2. 没有延迟类加载,可执行文件中提供的所有内容都将在启动时加载到内存中。
  3. 应用程序中定义的Bean不能在运行时更改,即bean创建相关的条件配置在编译后就不能再更改。
    如@Profile注解及类似配置不能更改,@ConditionalOnProperty中的条件值不能更改,即使更改也不会生效,因为bean已经创建了。

3. 原理简述

  1. Spring AOT Processor会启动应用main方法开始静态分析,并生成BeanDefinition对象,但不会创建bean
  2. 分析阶段不可达的代码将被忽略,不会编译到可执行文件中。
  3. 在编译前会生成java代码来创建BeanDefinition对象。源码存放到target/spring-aot/main/sources
  4. AOP原本在运行时动态生成的字节码,但在编译时就必须生成,动态字节码存放在target/spring-aot/main/classes
  5. Spring AOT Processor生成native需要的hint文件,存放到target/spring-aot/main/resources
  6. Spring boot把所有生成的文件都编译到jar包中,关于hint文件都放到jar包的META-INF/native-image目录下,graalvm静态编译时会获取该目录下的hint配置文件。

4. 官方文档阅读

Known GraalVM Native Image limitations

Testing GraalVM Native Images

Introducing GraalVM Native Images

5. 总结

  1. maven插件配置有两种方式,
    1. 一种是pom的parent指定为spring-boot-starter-parent后简单引入spring的spring-boot-maven-plugin和graalvm的native-maven-plugin插件
    2. 另一种是完整的配置spring的spring-boot-maven-plugin插件指定process-aot目标,配置graalvm的native-maven-plugin插件指定add-reachability-metadata目标
  2. 动态代码需要由开发者指定hint配置,有3种方式:
    1. 使用graalvm原生支持的方式,在META-INF/native-image目录下添加反射、代理、JNI等相关json配置。
    2. 实现RuntimeHintsRegistrar接口通过代码方式注册hint配置。
    3. 反射类可以使用@RegisterReflectionForBinding由spring自动绑定反射配置。
  3. 编译后影响bean创建相关的配置在运行期间不起作用,如@ConditionalOnProperty中的条件。
  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Spring Boot AOT (Ahead-Of-Time Compilation) 是指在Spring Boot应用程序启动之前将Java字节码编译成本地系统代码的过程,以提高应用程序的性能和响应速度。在Spring Boot 3.0或Spring Framework 6.0之后,我们可以直接使用Spring Framework 6.0内置的支持来处理AOT。我们可以通过自定义aot.factories文件配置来实现。未来,mica-auto也将支持使用注解来生成aot.factories文件。在Gradle构建工具中,我们可以在build.gradle文件中配置相关插件和依赖项,以支持Spring Boot AOT。例如,我们可以使用org.springframework.boot和io.spring.dependency-management插件,并在dependencies部分添加相应的依赖项。在应用程序代码中,我们需要使用@RestController和@SpringBootApplication注解来标记主类和控制器类。在主方法中,我们使用SpringApplication.run()方法启动应用程序。通过以上配置和代码,我们可以实现Spring Boot AOT。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Spring Boot 3.0 抢先了解:aot.factories 是个啥?](https://blog.csdn.net/j3T9Z7H/article/details/127437745)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Spring Boot 3的AOT(GraalVM Native Image)应用开发](https://blog.csdn.net/haiyan_qi/article/details/128057967)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值