SpringBoot3.x原生鏡像-Native Image嘗鮮
前提
Spring團(tuán)隊(duì)致力于為Spring應(yīng)用程序提供原生映像支持已經(jīng)有一段時(shí)間了。在SpringBoo2.x的Spring Native實(shí)驗(yàn)項(xiàng)目中醞釀了3年多之后,隨著Spring Framework 6和Spring Boot 3的發(fā)布,對應(yīng)的項(xiàng)目就是Spring Native,原生鏡像支持將會(huì)發(fā)布GA版本(換言之就是,Native Image相關(guān)支持會(huì)在Spring Boot 3的GA版本中一起發(fā)布)。
?前面這一段簡介摘抄自參考資料中的《Native Support in Spring Boot 3.0.0-M5》
?
筆者在寫這篇文章(2022-10-28)前后SpringBoot尚未發(fā)布3.x GA,版本3.0.0-M5+算是GA前相對穩(wěn)定的版本,這里選用當(dāng)前3.x的最新非GA版本3.0.0-RC1進(jìn)行調(diào)研。

什么是Native Image
Native Image,這里直譯為原生鏡像或者本地鏡像,是一種提前將(Java)代碼編譯為二進(jìn)制文件(原生可執(zhí)行文件,native executable)的技術(shù)。原生可執(zhí)行文件只包含運(yùn)行時(shí)所需要的代碼,即應(yīng)用程序類、標(biāo)準(zhǔn)庫類、語言運(yùn)行時(shí)和來自JDK的靜態(tài)鏈接的原生代碼(也就是**這樣的二進(jìn)制文件可以直接運(yùn)行,不需要額外安裝JDK**)。由原生鏡像生成的可執(zhí)行文件有幾個(gè)重要的優(yōu)點(diǎn):
使用 Java虛擬機(jī)所需資源的一小部分,因此運(yùn)行成本更低啟動(dòng)時(shí)間大幅度下降,以毫秒為單位 不需要進(jìn)行預(yù)熱即可提供最佳性能 可以打包到輕量級容器映像中以便快速有效地部署 減少了攻擊面(這個(gè)和網(wǎng)絡(luò)安全相關(guān))
Spring Boot 3中「使用GraalVM方案提供Native Image支持」。
安裝GraalVM
在https://www.graalvm.org/downloads - Download GraalVM頁面中下載對應(yīng)操作系統(tǒng)的GraalVM:

筆者開發(fā)環(huán)境使用的操作系統(tǒng)是Windows10,下載和選用下圖中的安裝包:

解壓完成后配置一下JAVA_HOME、GRAALVM_HOME并且把GRAALVM_HOME\bin添加到PATH中。完成后可以執(zhí)行一下java -version進(jìn)行驗(yàn)證:

?如果已經(jīng)安裝了其他版本的JDK,先暫時(shí)全局替換為GraalVM,也就是JAVA_HOME、GRAALVM_HOME同時(shí)配置為GraalVM的解壓目錄,因?yàn)槟壳翱磥磉@樣做才能正常打包原生鏡像
?
確定GraalVM版本無誤,到此安裝完成。另外,需要配置好了Maven,建議重新安裝一個(gè)3.6.x+版本的Maven并且把MAVEN_HOME\bin添加到PATH中。
編寫應(yīng)用程序
新建一個(gè)命名為spring-boot-native-image的Maven項(xiàng)目或者模塊,選用剛才下載好的GraalVM:

項(xiàng)目的POM文件引入下面幾組依賴:
spring的快照repository,因?yàn)樾枰螺dRC1版本依賴,暫時(shí)不能從中央倉庫拉取spring-boot-starter-parent,定義版本為RC1native-maven-plugin插件,用于原生鏡像打包spring-boot-starter-web,用于構(gòu)建一個(gè)簡單的web項(xiàng)目
<!-- spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0-RC1</version>
</parent>
<!-- repository -->
<repositories>
<repository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/snapshot</url>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
<!-- spring-boot-starter-web -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- native-maven-plugin -->
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.16</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<mainClass>cn.vlts.NativeApplication</mainClass>
<imageName>native-app</imageName>
<buildArgs>
<buildArg>--verbose</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
最終的POM文件看起來如下:

項(xiàng)目中只有一個(gè)啟動(dòng)類cn.vlts.NativeApplication,編寫了main方法和一個(gè)用于集成測試的控制器方法:
@RestController
@SpringBootApplication
public class NativeApplication {
public static void main(String[] args) {
SpringApplication.run(NativeApplication.class, args);
}
@GetMapping(path = "/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("world");
}
}
打包和調(diào)試
完成項(xiàng)目配置和代碼編寫后,執(zhí)行下面的Maven命令進(jìn)行打包:
mvn package -Pnative
打包過程可能會(huì)遇到下面的問題:
最有可能的問題: Default native-compiler executable 'cl.exe' not found via environment variable PATH
解決方案在Stackoverflow對應(yīng)的問題回答中找到:

其實(shí)就是在Window操作系統(tǒng)開發(fā)環(huán)境下基于GraalVM構(gòu)建原生鏡像依賴Microsoft Visual C++ (MSVC),建議安裝MSVC 2017 15.5.5+,可以安裝Visual Studio (2019)并且安裝對應(yīng)的MSVC。
因?yàn)楹茉缰肮P者在調(diào)試Rust時(shí)候已經(jīng)安裝過Visual Studio 2019用于其debug工具鏈,這里無須進(jìn)行安裝。在安裝Visual Studio勾選MSVC vXXX的組件進(jìn)行安裝即可,然后需要把對應(yīng)的MSVC工具的bin目錄添加到PATH中(這個(gè)目錄一般是VS_HOME\VC\Tools\MSVC\版本號\bin\Hostx64\x64):

其次可能遇到的問題:打包過程出現(xiàn) stdio.h庫文件報(bào)錯(cuò)或者找不到主類Main entry point class 'app.jar' not found x.y.Application
其實(shí)還是因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">MSVC的問題,在GraalVM文檔中有提示如下:

簡單來說就是「必須在Visual Studio自帶的命令行工具x64 Native Tools Command Prompt中執(zhí)行native image相關(guān)命令」,這個(gè)命令行工具初始化如下:

在x64 Native Tools Command Prompt中先進(jìn)入目標(biāo)項(xiàng)目根目錄,然后執(zhí)行mvn -Pnative package:

最終看到BUILD SUCCESS字眼,項(xiàng)目的target目錄下可以看到一個(gè).exe和一個(gè).jar文件,而.exe文件就是前面一直提到的「可執(zhí)行的二進(jìn)制文件」:

直接運(yùn)行它:

可以看到這個(gè)文件運(yùn)行完全不依賴外部Java虛擬機(jī),并且啟動(dòng)速度極快(600毫秒左右),可以用POSTMAN等工具測試程序接口:

到此可以驗(yàn)證程序功能正常。
小結(jié)
SpringBoot3.x原生鏡像正式發(fā)布后會(huì)是SpringBoot在云原生領(lǐng)域的一個(gè)巨大進(jìn)步,讓我們拭目以待。但是就目前來看,常用的Windows開發(fā)環(huán)境下想要嘗試native image技術(shù)需要解決比較多的問題,Linux和Unix平臺下尚未驗(yàn)證,希望后面的版本迭代能夠降低使用難度并且支持一個(gè)命令多平臺打包的功能。
參考資料:
Native Support in Spring Boot 3.0.0-M5 GraalVM - Quick Start Guide
(本文完 c-2-d e-a-20221030 封面自剃須)
