环境筹备

操作系统: 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

最简示例代码

我的项目的代码目录如下:

POM.xml文件

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <!-- 继承自Springboot的parent,因而内置了native的profile及plugin等信息 -->    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>3.0.3</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>cn.followtry.app</groupId>    <artifactId>spring-image-demo</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>spring-image-demo</name>    <description>测试Spring的native image</description>    <properties>        <java.version>17</java.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-actuator</artifactId>        </dependency>        <dependency>            <groupId>io.micrometer</groupId>            <artifactId>micrometer-registry-prometheus</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <build>        <plugins>        <!-- 构建原生镜像的插件 -->            <plugin>                <groupId>org.graalvm.buildtools</groupId>                <artifactId>native-maven-plugin</artifactId>                <configuration>                    <imageName>followtry-image</imageName>                    <buildArgs>                    <!-- 开发时可应用,放慢构建速度,部署时须要去掉 -->                        <buildArg>-Ob</buildArg>                    </buildArgs>                </configuration>            </plugin>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

jAVA代码

利用启动入口类:

package cn.followtry.app.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic 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 */@Servicepublic 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,如图

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

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-metadatamaven-resources-plugin:3.3.0:resourcesmaven-compiler-plugin:3.10.1:compilemaven-resources-plugin:3.3.0:testResourcesmaven-compiler-plugin:3.10.1:testCompilemaven-surefire-plugin:2.22.2:testspring-boot-maven-plugin:3.0.3:process-aotmaven-jar-plugin:3.3.0:jarspring-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.1sFinished generating 'followtry-image' in 1m 56s.Total time:  02:07 min

通过本地镜像编译后,生成的followtry-image可执行文件大小为68M,字节码编译后的jar包大小为20M。如图:

利用启动信息:

两种形式的比照信息

原Jar形式Native Image形式比照倍数
编译工夫3.96s127sNative编译慢32倍
启动工夫2.38s0.13sNative启动工夫快18倍
编译后大小20M68MNative包是Jar包的3.4倍

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

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

结语

本文次要解说了从环境装置,代码编写,编译启动,打包形式比照等方面简略介绍了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