SpringBoot3.x原生映象-Native Image嚐鮮

2022-10-30 12:00:07

前提

Spring團隊致力於為Spring應用程式提供原生映像支援已經有一段時間了。在SpringBoo2.xSpring Native實驗專案中醞釀了3年多之後,隨著Spring Framework 6Spring Boot 3的釋出,對應的專案就是Spring Native,原生映象支援將會發布GA版本(換言之就是,Native Image相關支援會在Spring Boot 3GA版本中一起釋出)。

前面這一段簡介摘抄自參考資料中的《Native Support in Spring Boot 3.0.0-M5》

筆者在寫這篇文章(2022-10-28)前後SpringBoot尚未釋出3.x GA,版本3.0.0-M5+算是GA前相對穩定的版本,這裡選用當前3.x的最新非GA版本3.0.0-RC1進行調研。

什麼是Native Image

Native Image,這裡直譯為原生映象或者本地映象,是一種提前將(Java)程式碼編譯為二進位制檔案(原生可執行檔案,native executable)的技術。原生可執行檔案只包含執行時所需要的程式碼,即應用程式類、標準庫類、語言執行時和來自JDK的靜態連結的原生程式碼(也就是這樣的二進位制檔案可以直接執行,不需要額外安裝JDK)。由原生映象生成的可執行檔案有幾個重要的優點:

  • 使用Java虛擬機器器所需資源的一小部分,因此執行成本更低
  • 啟動時間大幅度下降,以毫秒為單位
  • 不需要進行預熱即可提供最佳效能
  • 可以打包到輕量級容器映像中以便快速有效地部署
  • 減少了攻擊面(這個和網路安全相關)

Spring Boot 3使用GraalVM方案提供Native Image支援

安裝GraalVM

https://www.graalvm.org/downloads - Download GraalVM頁面中下載對應作業系統的GraalVM

筆者開發環境使用的作業系統是Windows10,下載和選用下圖中的安裝包:

解壓完成後設定一下JAVA_HOMEGRAALVM_HOME並且把GRAALVM_HOME\bin新增到PATH中。完成後可以執行一下java -version進行驗證:

如果已經安裝了其他版本的JDK,先暫時全域性替換為GraalVM,也就是JAVA_HOME、GRAALVM_HOME同時設定為GraalVM的解壓目錄,因為目前看來這樣做才能正常打包原生映象

確定GraalVM版本無誤,到此安裝完成。另外,需要設定好了Maven,建議重新安裝一個3.6.x+版本的Maven並且把MAVEN_HOME\bin新增到PATH中。

編寫應用程式

新建一個命名為spring-boot-native-imageMaven專案或者模組,選用剛才下載好的GraalVM

專案的POM檔案引入下面幾組依賴:

  • spring的快照repository,因為需要下載RC1版本依賴,暫時不能從中央倉庫拉取
  • spring-boot-starter-parent,定義版本為RC1
  • native-maven-plugin外掛,用於原生映象打包
  • spring-boot-starter-web,用於構建一個簡單的web專案
<!-- 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檔案看起來如下:

專案中只有一個啟動類cn.vlts.NativeApplication,編寫了main方法和一個用於整合測試的控制器方法:

@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");
    }
}

打包和偵錯

完成專案設定和程式碼編寫後,執行下面的Maven命令進行打包:

mvn package -Pnative

打包過程可能會遇到下面的問題:

  • 最有可能的問題:Default native-compiler executable 'cl.exe' not found via environment variable PATH

解決方案在Stackoverflow對應的問題回答中找到:

其實就是在Window作業系統開發環境下基於GraalVM構建原生映象依賴Microsoft Visual C++ (MSVC),建議安裝MSVC 2017 15.5.5+,可以安裝Visual Studio (2019)並且安裝對應的MSVC

因為很早之前筆者在偵錯Rust時候已經安裝過Visual Studio 2019用於其debug工具鏈,這裡無須進行安裝。在安裝Visual Studio勾選MSVC vXXX的元件進行安裝即可,然後需要把對應的MSVC工具的bin目錄新增到PATH中(這個目錄一般是VS_HOME\VC\Tools\MSVC\版本號\bin\Hostx64\x64):

  • 其次可能遇到的問題:打包過程出現stdio.h庫檔案報錯或者找不到主類Main entry point class 'app.jar' not found x.y.Application

其實還是因為MSVC的問題,在GraalVM檔案中有提示如下:

簡單來說就是必須在Visual Studio自帶的命令列工具x64 Native Tools Command Prompt中執行native image相關命令,這個命令列工具初始化如下:

x64 Native Tools Command Prompt中先進入目標專案根目錄,然後執行mvn -Pnative package

最終看到BUILD SUCCESS字眼,專案的target目錄下可以看到一個.exe和一個.jar檔案,而.exe檔案就是前面一直提到的可執行的二進位制檔案

直接執行它:

可以看到這個檔案執行完全不依賴外部Java虛擬機器器,並且啟動速度極快(600毫秒左右),可以用POSTMAN等工具測試程式介面:

到此可以驗證程式功能正常。

小結

SpringBoot3.x原生映象正式釋出後會是SpringBoot在雲原生領域的一個巨大進步,讓我們拭目以待。但是就目前來看,常用的Windows開發環境下想要嘗試native image技術需要解決比較多的問題,LinuxUnix平臺下尚未驗證,希望後面的版本迭代能夠降低使用難度並且支援一個命令多平臺打包的功能。

參考資料:

(本文完 c-2-d e-a-20221030)