## 前提 之前曾經寫過一篇[《SpringBoot3.x 原生鏡像-Native Image 嘗鮮》](https://vlts.cn/post/spring-boot-native-image-demo),當時`SpringBoot`處於`3.0.0-M5`版本,功能尚未穩定。這次會基於`Spr ...
前提
之前曾經寫過一篇《SpringBoot3.x 原生鏡像-Native Image 嘗鮮》,當時SpringBoot
處於3.0.0-M5
版本,功能尚未穩定。這次會基於SpringBoot
當前最新的穩定版本3.1.2
詳細分析Native Image
的實踐過程。系統或者軟體版本清單如下:
組件 | 版本 | 備註 |
---|---|---|
macOS Ventura |
13.4.1(c) |
ARM 架構 |
sdkman |
5.18.2 |
JDK 和各類SDK 包管理工具 |
Liberica Native Image Kit |
23.0.1.r17-nik |
可以構建Native Image 的JDK |
SpringBoot |
3.1.2 |
使用當前(2023-08-20 )最新發佈版 |
Maven |
3.9.0 |
- |
安裝 sdkman
sdkman是一個輕量級、支持多平臺的開源開發工具管理器,可以通過它安裝任意主流發行版本(例如OpenJDK
、Kona
、GraalVM
等等)的任意版本的JDK
。通過下麵的命令可以輕易安裝sdkman
:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version
可以通過sdk list java
查看支持的JDK
發行版本:
通過shell
命令sdk install java $Identifier
就可以安裝對應的JDK
發行版。例如可以這樣安裝GraalVM-ce-17
:
sdk install java 17.0.8-graalce
通過shell
命令sdk uninstall java $Identifier
可以卸載對應的JDK
發行版。如果安裝了多個版本或者多個發行版的JDK
,可以通過shell
命令sdk default java $Identifier
去指定預設使用的JDK
版本,例如:
sdk default java 17.0.8-graalce
可以通過shell
命令sdk current
或者sdk current java
查看當前正在使用的SDK
或者JDK
版本。
安裝 Liberica NIK
Liberica Native Image Kit
是bellsoft
出品的旨在創建高性能原生二進位(Native Binaries
)基於JVM
編寫的應用的工具包,簡稱為Liberica NIK
。Liberica NIK
本質就是把OpenJDK
和多種其他工具包一起封裝起來的JDK
發行版,在Native Image
功能應用過程,可以簡單把它視為OpenJDK
+ GraalVM
的結合體。可以通過sdk list java
查看相應的JDK
版本:
這裡選擇JDK-17
的版本進行安裝:
sdk install java 23.0.1.r17-nik
# 這裡最好把此JDK設置為當前系統的預設JDK,否則後面編譯鏡像時候會提示找不到GraalVM
sdk default java 23.0.1.r17-nik
安裝完成後,通過java -version
驗證一下:
編寫 SpringBoot 應用
基於Maven
新建一個SpringBoot
應用,這裡已經整理好了一份POM
文件,實踐過程可以直接用,如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.vlts</groupId>
<artifactId>spring-boot-native-image-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.version>3.11.0</maven.compiler.version>
<maven.install.version>3.1.1</maven.install.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.experimental</groupId>
<artifactId>tomcat-embed-programmatic</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven.install.version}</version>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>cn.vlts.NativeImageApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
這裡把Maven
的所有插件都提升到當前(2023-08-20
前後)最新版本,原生鏡像打包的關鍵插件是native-maven-plugin
,此插件是跟隨spring-boot-starter-parent
進行版本管理,這裡無須指定插件的版本。另外,tomcat-embed-programmatic
是一個實驗性依賴,可以降低嵌入式Tomcat
的記憶體使用,在生產中應用時候可以暫不啟用此特性。接著編寫啟動類cn.vlts.NativeImageApplication
:
@SpringBootApplication
@RestController
public class NativeImageApplication {
public static void main(String[] args) {
SpringApplication.run(NativeImageApplication.class, args);
}
@RequestMapping(path = "/")
public ResponseEntity<String> index() {
return ResponseEntity.ok("index");
}
}
構建、測試與發佈
三個操作的Maven
命令分別是:
- 構建:
mvn -Pnative native:compile
- 測試:
mvn -PnativeTest test
- 發佈:
mvn -Pnative spring-boot:build-image
,註意此命令會打包鏡像並且發佈到Docker
的官方倉庫中
雖然 native:compile 命令錶面意義是編譯,但是實際上它就是構建原生鏡像的命令
執行構建流程:
mvn -Pnative native:compile -Dmaven.test.skip=true
構建結果如下:
其中這個不帶.jar
尾碼的就是最終的原生鏡像,並且Native Image
是不支持跨平臺的,它只能在ARM
架構的macOS
中運行(受限於筆者的編譯環境)。可以發現它(見上圖中的target/spring-boot-native-image-demo
,它是一個二進位執行文件)的體積比executable jar
大好幾倍。參照SpringBoot
的官方文檔,經過AOT
編譯的SpringBoot
應用會生成下麵的文件:
Java
源代碼- 位元組碼(例如動態代理編譯後的產物等)
GraalVM
識別的提示文件:- 資源提示文件(
resource-config.json
) - 反射提示文件(
reflect-config.json
) - 序列化提示文件(
serialization-config.json
) Java
(動態)代理提示文件(proxy-config.json
)JNI
提示文件(jni-config.json
)
- 資源提示文件(
這裡的輸出非執行包產物基本都在target/spring-aot
目錄下,其他非Spring
或者項目源代碼相關的產物輸出到graalvm-reachability-metadata
目錄中。最後可以驗證一下產出的Native Image
:
可以看到啟動速度達到驚人的毫秒級別,如果應用在生產中應該可以全天候近乎無損發佈。當然,理論上Native Image
性能也會大幅度提升,但是限於篇幅這裡暫時不進行性能測試。
小結
鑒於SpringBoot3.x
的正式版已經推出一段時間,從文檔上看,Native Image
使用的技術已經相對成熟,可以放心用於生產環境。當然,Native Image
目前還存在一些局限性會讓一些組件完全無法使用或者部分功能受限(參考Spring Boot with GraalVM),希望這些問題或者局限性有一天能夠突破讓所有JVM
應用迎來一次性能飛躍。
(本文完 c-2-d e-a-20230820)