共计 6828 个字符,预计需要花费 18 分钟才能阅读完成。
环境筹备
操作系统:MacOS Monterey 12.5.1
CPU: Intel I7
装置java17
- 从 Oracle 下载 java17 对应版本,并装置在 Mac 零碎中
- 设置环境变量便于疾速切换 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
- 下载对应零碎对应 JDK 版本的 Graalvm,下载页面地址:https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-22.3.1
-
设置 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 -
装置
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; | |
@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
,如图
执行命令 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-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
。如图:
利用启动信息:
两种形式的比照信息
原 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 编译时类肯定存在。
另一个比拟要害的是在 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