Spring Native Beta 來了,原生更香!

作者 | Spring 官方博客

譯者 | 張衛濱

策劃 | 萬佳

Spring 團隊日前發佈了 Spring Native Beta 版。通過 Spring Native,Spring 應用將有機會以 GraalVM 原生鏡像的方式運行。爲了更好地支持原生運行,Spring Native 提供了 Maven 和 Gradle 插件,並且提供了優化原生配置的註解。

最近,Spring 發佈了 Spring Native 的 beta 版本,該功能已經在 start.spring.io 上可用了。

https://github.com/spring-projects-experimental/spring-native

https://start.spring.io/

這意味着,除了 Spring 誕生以來就支持的 Java 虛擬機,官方添加了使用 GraalVM 將 Spring 應用編譯成原生鏡像的 beta 支持,這樣的話,就能提供一種新的方式來部署 Spring 應用。Spring Native 支持 Java 和 Kotlin。

這些原生的 Spring 應用可以作爲一個獨立的可執行文件進行部署(不需要安裝 JVM),並且還能提供有趣的特徵,包括幾乎瞬時的啓動(一般會小於 100 毫秒)、瞬時的峯值性能以及更低的資源消耗,其代價是比 JVM 更長的構建時間和更少的運行時優化。

通過簡單的mvn spring-boot:build-imagegradle bootBuildImage命令,就能生成一個優化的容器鏡像,它包含了一個最小的操作系統層和一個小的原生可執行文件,該文件只包含了必需的東西即 JDK、Spring 以及應用中所使用的依賴。

請看下面這個最小的容器鏡像,它是一個 50MB 的可執行文件,包含了 Spring Boot、Spring MVC、Jackson、Tomcat、JDK 和應用本身。

這種原生方式,在很多場景下都會對 Spring 應用產生價值:

在使用場景上,比如 Piotr Mińkowski 提供了一個非常棒的指南,介紹瞭如何在 Knative 上使用 Spring Boot 和 GraalVM 構建原生微服務。

https://piotrminkowski.com/2021/03/05/microservices-on-knative-with-spring-boot-and-graalvm/

1 團隊協作

Spring Native beta 是整個 Spring 團隊及其家族項目廣泛合作的結果:Spring Framework、Spring Boot 還包括 Spring Data、Spring Security、Spring Cloud 和 Spring Initializr。

據悉,原生功能的工作範圍比 Spring 更廣,因爲原生涉及到更廣泛的 JVM 生態系統,所以官方一直在與 GraalVM 團隊合作,以改善原生鏡像的兼容性和資源消耗。

以下是來自 GraalVM 團隊的 Vojin Jovanovic 的一段話。

“與 Spring 團隊協作打造原生 JVM 生態系統是一件非常愉快的事情:他們深厚的技術知識,再加上對社區的敏感觸覺,總是能帶來最好的解決方案。最新的 Spring Native 版本,以及它在 JVM 生態系統中的衆多用法,爲原生編譯的廣泛採用鋪平了道路。”

2 支持的範圍

現在,Spring Native 已經從 alpha 版本畢業成爲 beta,那麼很重要的一點就是明確它所支持的功能範圍。

Alpha 版本是第一步,實驗了很多東西,並且基於一組樣例改善了 Spring Native(之前叫做 Spring GraalVM Native)的架構、兼容性和資源消耗,其中有很多破壞性的變更。官方還報告了很多問題,這些問題 GraalVM 團隊已經解決,從而減少 JVM 和 Spring 應用的原生鏡像之間的差距。

雖然它依然被認爲是實驗性的,但 beta 版意味着 Spring 現在在 Spring 生態系統的一個子集上提供了對原生的支持。如果你的應用正在使用業已支持的依賴,那麼你可以試用它,在出現問題時可以提 bug 或貢獻 pull request。在最新的 Spring Boot 2.x 小版本的每個補丁發佈時,都會有一個新的 Spring Native 版本。Spring Native 0.9.0 支持 Spring Boot 2.4.3,Spring Native 0.9.1 將支持 Spring Boot 2.4.4 等。

3start.spring.io

Stéphane Nicoll 在對 start.spring.io 和相關 IDE 的集成中,引入了對 Spring Native 的支持,所以現在這是探索如何使用 Spring 構建原生應用最簡單的方式。

添加 Spring Native 依賴後將會使用所需的依賴和插件自動配置 Maven 或 Gradle 項目,以便於支持原生。應用代碼本身沒有變化。

請檢查自動生成的HELP.md文件,該文件包含了有用的鏈接和文檔,同時它還能標記出來你是否選擇了一些在原生環境下不支持的依賴。

4 預先轉換

原生與 JVM 有所不同:類路徑在構建時是固定的,反射或資源需要進行配置,這裏沒有類的懶加載(可執行文件中包含的所有內容在啓動的時候都會加載進來)並且有些代碼可以在構建期調用。

爲了充分擁抱這些特性,並且能讓 Spring 應用以最大的兼容性和最小的資源消耗運行在原生環境中,Brian Clozel 在這個版本中引入了 Spring 預先(ahead-of-time,AOT)轉換的 Maven 和 Gradle 插件,這個插件會對 Spring 應用執行預先轉換。

第一種轉換的目的是生成 GraalVM 原生配置(反射、資源、代理、原生鏡像選項),這是通過由 Andy Clement 設計和實現的一個特別棒的推斷引擎做到的,該引擎能夠理解 Spring 編程模型和基礎設施。例如,每個帶有 @Controller 註解的類,都會在生成的 reflect-config.json 文件中添加一個條目。

有些原生配置是無法推斷的,對於這些情況,Spring Native 引入了原生線索(native hint)註解(參見 Javadoc 以瞭解詳情),這些註解允許 Spring Native 支持原生配置,這種方式比常規的基於 JSON 的原生鏡像配置更加可維護、類型安全和靈活。例如,Spring Native 對 MySQL 驅動支持就提供了線索註解,它們會在原生鏡像配置 reflect-config.json、resource-config.json 和 native-image.properties 中生成正確的條目,如下所示:

 1@NativeHint(
 2    trigger = Driver.class,
 3    options = "--enable-all-security-services",
 4    types = @TypeHint(types = {
 5       FailoverConnectionUrl.class,
 6       FailoverDnsSrvConnectionUrl.class,
 7       // ...
 8    }), resources = {
 9    @ResourceHint(patterns = "com/mysql/cj/TlsSettings.properties"),
10    @ResourceHint(patterns = "com.mysql.cj.LocalizedErrorMessages",
11                      isBundle = true)
12})
13public class MySqlHints implements NativeConfiguration {}
14

NativeConfiguration 和其他的動態配置機制允許實現更加強大和動態化的配置生成,但是需要注意它們的 API 在未來的版本中可能會有很大變化。

Spring 開發人員也可以直接在 @Configuration 或 @SpringBootApplication 類上添加應用特定的原生線索註解,例如,對於使用 RestTemplate 或 WebClient 這樣的編程 API 序列化一個 Book 類爲 JSON:

1@TypeHint(types = Book.class)
2@SpringBootApplication
3public class WebClientApplication {
4    // ...
5}
6

在使用預先轉換系統時,最後一個,可能也是最強大的一個機制就是根據 Spring Boot 部署模型和 GraalVM 原生鏡像特徵所引入的封閉世界(closed-world)假設,它能夠自動生成針對原生環境進行優化的代碼。這裏的目標就是限制所需的外部原生配置的數量,從而提高兼容性,這是通過原生鏡像編譯器對代碼結構的分析實現的,同時還能通過減少反射、資源或代理所需的配置,降低資源佔用。一個具體的例子就是對各種 spring.factory(Spring Boot 背後的擴展機制)的預先轉換,從而實現一個優化過的程序版本,該版本不需要反射並且會過濾掉應用上下文中不必要的條目。

對 Spring AOT 來說,這只是一個開始,我們計劃添加更加強大的轉換,比如將 @Configuration 替換爲函數式配置,從而通過預先分析替換運行時反射,能夠自動生成使用像 lambda 表達式和方法引用這種程序構造的配置類。這樣的話,就能允許 GraalVM 原生鏡像編譯器立即理解 Spring 配置,無需任何的反射配置或 *.class 資源。

需要記住的一個關鍵點是,在使用 Spring Native 時,這個 AOT 生成的代碼在 JVM 上也會默認使用,這樣的話能夠通過 JVM 允許的短反饋循環(short feedback loop),用調試器和所有常規工具實現 “原生友好的代碼路徑”。

儘管 Spring AOT 轉換目前主要是由原生場景需求驅動,但是有很多轉換並不是特定於原生場景的,有一些可能爲 JVM 上運行的 Spring Boot 應用提供優化。和往常一樣,對於這種主題,重要的是要以數據爲驅動,所以我們會衡量效率和性能來驅動我們的決策。

我們很可能會完善 IDE 集成,目前請務必閱讀相關文檔,瞭解潛在的手動配置步驟,以便在 IDE 中運行應用程序之前更新生成的源碼。

5 結論

在支持原生方面,Spring 有兩個支柱性的策略。第一個是在不需要對現有的數百萬個 Spring Boot 應用進行重大改動的情況下,對 Spring 基礎架構進行調整以適應原生。這包括在 Spring 頂層項目中爲實現原生友好而做出的改變,像 @NativeHint 這樣的基礎架構,以及在 Spring Native 中逐漸成熟的 Spring AOT 構建插件。

第二個支柱比 Spring 本身的範圍更廣,原生是一個與 JVM 特性有所差異的平臺,但 Java 生態系統需要儘可能地保持一致,以避免出現兩種截然不同的 Java 風格,如果這樣的話,將會是維護上的一個挑戰。

原文鏈接:

https://spring.io/blog/2021/03/11/announcing-spring-native-beta

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nsgzbBTo6ic61eai8_dH_A