Spring AOT应用实践

环境准备

操作系统: MacOS Monterey 12.5.1

CPU: Intel I7

安装java17

  1. 从Oracle下载java17对应版本,并安装在Mac系统中
  2. 设置环境变量便于快速切换shell的环境。以当前用户的zsh为例,当前用户home下的.zshrc文件中增加内容
# 指定java17的home目录
export JAVA_17_HOME='/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/
Home'
# 快速将JAVA的HOME指定为java17的home目录,系统上安装多jdk版本时好用
alias java17="export JAVA_HOME=$JAVA_17_HOME"
# 设置maven别名,在使用maven命令时先设置当前shell的java环境
alias mvn17='java17;mvn '

在IDE中开发代码直接指定目录设置项目的JDK版本为java17即可,建议使用最新版本的IDEA

安装Graalvm

  1. 下载对应系统对应JDK版本的Graalvm,下载页面地址: https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-22.3.1
  2. 设置Graalvm的home目录

    # 将Graalvm的home路径添加到系统变量中
    export GRAALVM_HOME='/Users/{userName}/{path}/graalvm-ce-java17-amd64/Contents/Home'
    # 将graalvm的bin目录添加到系统path中,可以直接使用bin下的命令,不再需要完整的路径
    export PATH=$GRAALVM_HOME/bin:$PATH
  3. 安装native-image

    $gu install native-image
    # 或者使用命令全路径
    $GRAALVM_HOME/bin/gu install native-image

最简示例代码

项目的代码目录如下:

Spring AOT应用实践_第1张图片

POM.xml文件



    4.0.0
    
    
        org.springframework.boot
        spring-boot-starter-parent
        3.0.3
         
    
    cn.followtry.app
    spring-image-demo
    0.0.1-SNAPSHOT
    spring-image-demo
    测试Spring的native image
    
        17
    
    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
            io.micrometer
            micrometer-registry-prometheus
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
        
        
            
                org.graalvm.buildtools
                native-maven-plugin
                
                    followtry-image
                    
                    
                        -Ob
                    
                
            
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

jAVA代码

应用启动入口类:

package cn.followtry.app.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FollowtryImageApplication {
    public static void main(String[] args) {
        SpringApplication.run(FollowtryImageApplication.class, args);
    }
}

测试用的Service

package cn.followtry.app.demo.service;

import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * @author followtry
 * @since 2023/2/24 16:55
 */
@Service
public class HelloService {

    private static final Logger log = LoggerFactory.getLogger(HelloService.class);

    @PostConstruct
    public void init() {
        System.out.println("HelloService.init");
        log.info("HelloService.init");
    }

    public String sayHello(String name) {
        String msg = "hello," + name;
        log.info(msg);
        return msg;
    }
}

controller如下:

package cn.followtry.app.demo.web;

import cn.followtry.app.demo.service.HelloService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author followtry
 * @since 2023/2/25 20:18
 */
@RestController
@RequestMapping("test")
public class HelloController {

    private final HelloService helloService;

    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("hello")
    public String hello(String name) {
        return helloService.sayHello(name);
    }
}

为了支持java17的编译,需要对maven添加编译参数.如目录.mvn下的jvm.config

内容如下:

--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED
--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
-Dio.netty.tryReflectionSetAccessible=true

可以看出,应用代码基本没什么特别之处,但就这样的普通代码就可以最终被编译成本地可执行的镜像文件。

编译打包

原jar包的打包方式

需执行命令mvn17 clean package(mvn17来自于文章初始部分自定义的alias),编译后的jar文件spring-image-demo-0.0.1-SNAPSHOT.jar大小为20M,且打包耗时3.96,如图

Spring AOT应用实践_第2张图片

执行命令java -jar ./target/spring-image-demo-0.0.1-SNAPSHOT.jar启动java应用,从图中可以看出应用启动完耗时2.38秒,接口/test/hello?name=zhangsan可以正常访问。

Spring AOT应用实践_第3张图片

native image打包方式

执行命令mvn17 clean native:compile -Pnative,经历步骤日志的关键信息包括

Scanning for projects...
maven-clean-plugin:3.2.0:clean
# native 编译前先执行类可达性分析,将需要编译的类重新生成元数据信息
native-maven-plugin:0.9.20:compile 
native-maven-plugin:0.9.20:add-reachability-metadata
maven-resources-plugin:3.3.0:resources
maven-compiler-plugin:3.10.1:compile
maven-resources-plugin:3.3.0:testResources
maven-compiler-plugin:3.10.1:testCompile
maven-surefire-plugin:2.22.2:test
spring-boot-maven-plugin:3.0.3:process-aot
maven-jar-plugin:3.3.0:jar
spring-boot-maven-plugin:3.0.3:repackage
# 需要的类元数据信息重新生成完后,开始执行Native编译

native-maven-plugin:0.9.20:compile
    GraalVM Native Image: Generating 'followtry-image' (executable)...
    [1/7] Initializing...   cost 11.4s
    [2/7] Performing analysis   cost 53.2s
    [3/7] Building universe...  cost 5.4s
    [4/7] Parsing methods...    cost 5.5s
    [5/7] Inlining methods...   cost 2.3s
    [6/7] Compiling methods..   cost 21.1s
    [7/7] Creating image...     cost 9.1s
Finished generating 'followtry-image' in 1m 56s.
Total time:  02:07 min

经过本地镜像编译后,生成的followtry-image可执行文件大小为68M,字节码编译后的jar包大小为20M。如图:
Spring AOT应用实践_第4张图片

应用启动信息:

Spring AOT应用实践_第5张图片

两种方式的对比信息

原Jar方式 Native Image方式 对比倍数
编译时间 3.96s 127s Native编译慢32倍
启动时间 2.38s 0.13s Native启动时间快18倍
编译后大小 20M 68M Native包是Jar包的3.4倍

如文章(http://george5814.github.io/2023-02-24/spring-aot.html)中所说,SpringAOT在执行后会生成Java类对应的BeanDefinition的class信息,该步骤是在process-aot时完成的。将打好的jar包解压后可以看到如图中增加的几种字节码文件,该文件即为将注解解析后生成的类编译而成,是为了在graalvm执行native 编译时类一定存在。

Spring AOT应用实践_第6张图片

另一个比较关键的是在META-INF中生成的反射、资源等的配置文件。已反射的配置文件reflect-config.json为例,如下图中示例,可看出SpringBoot的maven插件已经自定找到反射类信息并将其作为配置进行生成。

Spring AOT应用实践_第7张图片

结语

本文主要讲解了从环境安装,代码编写,编译启动,打包方式对比等方面简单介绍了SpringBoot3.0 在native image的入门使用,其中的AOT原理机制解析待后续文章继续输出。

参考文章

https://www.baeldung.com/spring-native-intro#overview-1
https://graalvm.github.io/native-build-tools/latest/graalvm-s...
https://github.com/graalvm/graalvm-demos/tree/master/spring-native-image

你可能感兴趣的:(Spring AOT应用实践)