發表日期 5/21/2022, 10:21:13 AM
作者 | Graeme Rocher
譯者 | 明知山
策劃 | 丁曉昀
本文是“Native Compilations Boosts Java”係列文章的一部分。你可以通過訂閱 RSS 接收更新通知。
Java 主導著企業級應用。但在雲計算領域,采用 Java 的成本比它的一些競爭對手更高。原生編譯降低瞭在雲端采用 Java 的成本:用它創建的應用程序啓動速度更快,使用的內存更少。
那麼,Java 用戶的問題來瞭:原生 Java 是如何改變開發方式的?我們在什麼情況下應該切換到原生 Java?什麼情況下又不應該切換?我們應該使用什麼框架?本係列文章將迴答這些問題。
對服務器端 Java 認識的改變
2017 年,Java 服務器端齣現瞭一個認知問題。隨著嚮微服務和輕量級容器化運行時的轉變,開發人員開始注意到傳統 Java 應用程序的膨脹,它們被打包並部署到 Servlet 容器的共享 Java 虛擬機 (JVM) 上。Serverless 的齣現進一步加劇瞭這種認知。
正是在這段時間,Object Computing 的一個團隊開始重新思考如何從頭開始設計 Java 框架。於是 Micronaut 框架誕生瞭,這是一個采用瞭不同做法的 Java 框架,它通過使用 Java 注釋將框架的組裝計算工作所轉移到瞭編譯階段。這完全消除瞭傳統 Java 框架使用的反射、運行時生成代理和復雜的動態類加載。
2018 年 4 月,Micronaut 框架首次公開發布,引發瞭 Java 社區巨大的思維變化,改變瞭人們對 Java“緩慢而臃腫”的看法。許多較新的項目也采取瞭類似的做法:將更多的邏輯轉移到應用程序的構建和編譯階段,以此來優化應用程序的啓動性能和消除反射。
構建編譯時的好處很明顯:在編譯時計算更多的東西,那麼在運行時就可以以最優的方式執行。消除瞭反射、動態類加載和運行時生成代理,為我們提供瞭進一步的下遊優化機會,包括 JIT 和 (關鍵的是)GraalVM 的原生鏡像工具。因為采用瞭這種方法,原生鏡像不需要額外的配置信息就可以對 Micronaut 框架應用程序進行靜態分析。
由於 Micronaut 框架和 GraalVM 之間存在這種協同作用,Micronaut 框架聯閤創始人 Graeme Rocher 加入瞭 Oracle Labs。Oracle Labs 不僅有 GraalVM,也為正在進行的 Micronaut 框架的開發做齣瞭重大貢獻。
Micronaut 框架介紹
人們對 Micronaut 框架的一個常見誤解是,它是專為微服務設計的。事實上,Micronaut 框架也為一係列應用程序類型提供瞭一種極限模塊化的架構!
Micronaut 框架實現瞭 JSR-330 依賴注入規範,並提供瞭許多附加的內置特性,是一個絕佳的基於注解編程模型的通用框架。它的特性包括:
配置注入;
AOP 編程概念,如攔截器;
內置瞭對雲原生應用程序基本概念的支持,如驗證、緩存、彈性重試、作業調度等。Micronaut 基於 Netty I/O 工具包構建瞭一個 HTTP 服務器和 HTTP 客戶端。
用戶已經用 Micronaut 框架來構建無服務器應用程序、命令行應用程序,甚至是 JavaFX 應用程序。
Micronaut 框架為廣泛的模塊生態係統提供瞭基礎,Micronaut 可以幫助它們解決一係列問題。正是由於這種靈活性,Micronaut 框架在開發者當中得到瞭極大的普及。以下 Micronaut 的架構圖:
基礎層基於 Java Annotation Processing (APT),實現瞭編譯時依賴注入,支持各種模塊的構建,包括基於 Netty 的 HTTP 服務器。但它也涵蓋瞭其他領域,如數據訪問、安全性和 JSON 序列化。
為什麼要用 Micronaut 框架?
Micronaut 框架的目標是完全消除框架中使用的 Java 反射、動態類加載和運行時生成的代理和字節碼等特性,以此來提供傳統 Java 框架的輕量級替代方案。
消除傳統框架對這些特性的依賴對提高性能、內存消耗、安全性、健壯性、調試和測試的便捷性有著深遠的影響。與其他解決方案不同的是,Micronaut 框架應用程序也可以在 JVM 中快速啓動!
因為啓動速度得到瞭極大改進,就沒有必要再區分集成測試和單元測試代碼,這極大縮短瞭從編碼到測試之間的時間。在過去,我們常常因為應用程序啓動太慢不得不減少集成測試。Micronaut 框架消除瞭這種情況,所以框架中沒有包含大量 HTTP 層的模擬工具。其他的許多框架之所以提供大量的模擬工具,是為瞭降低啓動應用程序的成本。
消除反射減少瞭堆棧跟蹤信息的數量,而在傳統框架中,堆棧跟蹤信息通常非常繁雜。
Micronaut 框架還提供瞭將代碼轉換成構建時編譯的機製和 API。Micronaut 框架直接與 Java 編譯器集成,當注解使用不當時,它會生成編譯錯誤,從而提高代碼的類型安全性和整體開發者體驗。
Micronaut 框架入門
本節將介紹如何使用 Micronaut 框架來構建雲原生 Java 微服務。
使用 Micronaut 框架有幾種不同的方法。你至少需要 Java SE 8 或更高版本的 JDK。如果要使用原生鏡像特性,你需要 Java 11 或更高版本的 GraalVM JDK。
要創建一個 Micronaut 應用程序,你可以使用已經集成到 IDE(例如,IntelliJ IDEA Ultimate 或 GraalVM Tools 的 VSCode Micronaut 擴展)中的嚮導。
另外,通過 Micronaut Launch 創建一個新的 Micronaut 應用程序也非常容易。它是一個項目創建嚮導,你可以選擇想要構建的應用程序類型和要包含的特性。然後,它會生成一個包含應用程序的 ZIP 文件,你可以將下載它,或者將代碼推送到你的 Github 存儲庫。
如果你對命令行更熟悉,還可以通過常見方法 (包括 SDKMAN、Homebrew) 安裝 Micronaut CLI 來創建應用程序。在安裝好以後,創建一個新的應用程序就很簡單:
如果你不喜歡安裝額外的 CLI,可以通過 curl 直接調用 Micronaut Launch API:
上麵的命令使用 Gradle 構建工具創建瞭一個應用程序。你也可以將“gradle”替換成“maven”。
Micronaut 框架生成的項目結構與其他 Java 項目一樣:
一個 Gradle 或 Maven 構建文件 (盡管也可以配置其他的構建工具,如 Bazel)。
默認配置文件是 src/main/resources/application.yml。但是,如果你不喜歡 YAML,可以使用 Java 屬性、JSON、HOCON 或 TOML 作為替代。
默認的日誌記錄器是 SLF4J+Logback 的組閤,配置文件為 src/main/resources/logback.xml。你也可以將 SLF4J 換成其他日誌記錄係統。
單元測試是 JUnit 5,但也支持其他測試框架,如 Spock 和 Kotest for Kotlin 等。一個新創建的項目提供瞭一些 Java 源代碼來幫助你入門。第一個是位於 src/main/java 中的 Application.java 類,它包含瞭 Micronaut 應用程序的主入口點:
對 Micronaut.run(..) 的調用將觸發框架的啓動過程。
第二個類在 src/test/java 目錄中,用於驗證應用程序可以成功啓動,而且沒有任何錯誤:
這個 JUnit 5 測試用例用 @MicronautTest 進行瞭注解。這個注解是一個 JUnit 5 擴展,用於將組件注入到測試中。在本例中,將為運行中的應用程序注入 EmbeddedApplication。
為 Micronaut 開發準備 IDE
一般來說,Micronaut 框架基於 Java Annotation Processing(APT) 的優勢之一是使用這個框架時不需要其他特殊的構建工具。所有流行的 IDE 都支持 APT,盡管有些 IDE(如 Eclipse) 需要顯式地啓用它。
隨著 Micronaut 框架越來越流行,IDE 廠商已經提供對這個框架的支持。JetBrain 的 IntelliJ Ultimate 就為這個框架的用戶提供瞭優秀的工具,包括項目嚮導、配置自動完成、Micronaut 數據支持等。
此外,Visual Studio Code 有免費的 GraalVM 擴展包,包括一個 Micronaut 項目創建嚮導、配置自動完成以及 Micronaut 應用程序的原生鏡像功能。
如果你安裝瞭這些 IDE 中的任何一個,隻需在 IDE 中打開 Gradle 或 Maven 項目,一切就都設置好瞭,你就準備就緒瞭。
開發 REST API
Micronaut 框架支持廣泛的服務器端工作負載,包括 REST、gRPC、GraphQL 和基於 Kafka、RabbitMQ、JMS 和 MQTT 消息驅動的微服務。本文將重點介紹使用默認的基於 Netty 的 HTTP 服務器構建 REST 應用程序。
Micronaut 應用程序中的每個 HTTP 路由都通過一個帶 @Controller 注解的 Java 類來定義。注解的名稱來源於 Model View Controller(MVC)模式。帶 @Controller 注解的類可以包含一個或多個映射到特定 HTTP 動詞和 URI 的方法。
“Hello World”示例可以通過 Micronaut 控製器來實現,如下所示:
控製器被映射到瞭 /hello。帶 @Get 注解的方法負責處理 HTTP GET 請求,並使用 RFC 5741 URI 模闆綁定瞭方法的 name 參數。你可以在 IDE 中運行 Application 類的 main 方法或通過./gradlew run 或./mvnw mn:run 來啓動服務器。然後,你可以通過嚮 Micronaut HTTP 服務器的默認 8080 端口發送 curl 請求來測試端點:
既然 Micronaut 框架非常注重測試,那麼還有什麼比單元測試更好的方法來測試 API 呢?下麵是 HelloController 示例的一個簡單的 JUnit 5 測試:
上麵的測試注入瞭一個 Micronaut 的 HTTP Client,嚮 /hello/John URI 發送瞭一個 GET 請求,並斷言結果是正確的。
Micronaut 框架的一個巨大好處是測試執行得非常快,可以與常規單元測試相媲美。即使 @MicronautTest 注解啓動瞭 Micronaut 服務器,並運行瞭完整的 HTTP 請求響應周期,執行速度也不會受到影響。這樣就沒有必要再去學習大量用於模擬 HTTP 服務器的 API 瞭!開發人員因此可以編寫更多的集成測試,提高代碼可維護性和質量。
訪問數據庫
訪問數據庫是服務器端應用程序的一種非常常見的活動,因此許多框架都為此提供瞭簡化,以提高開發人員在這方麵的生産力。Micronaut 框架也不例外。
Micronaut Data 是一個具有特殊功能的數據庫訪問工具包:通過與 Micronaut 編譯器的集成,Micronaut Data 增加瞭數據庫查詢的編譯時檢查和構建時計算,從而提高瞭運行時效率。
與 Spring Data JPA 非常相似,Micronaut Data 允許你使用 Repository 模式定義 Java 接口,它會在編譯時自動為你實現數據庫查詢。
Micronaut Data 對 Repository 接口的方法簽名進行編譯時分析,並在可能的情況下實現接口,否則將發生編譯錯誤。
Micronaut Data 支持多種不同的數據庫和查詢格式,包括:
Hibernate 和 JPA――你可以使用 JPA 和 Hibernate,並且 Micronaut Data JPA 會在編譯時計算 JPA 查詢 (如上所述)。
JDBC 和 SQL――對於那些更喜歡原始 SQL 和簡單的數據映射而不是對象關係映射 (ORM) 的人來說,Micronaut Data JDBC 提供瞭一個更簡單的解決方案,可以用它嚮關係數據庫寫入或讀取 Java 17+ 的記錄類對象和 POJO。
MongoDB――作為最新添加的功能,Micronaut Data MongoDB 直接與 MongoDB 驅動程序集成,Micronaut 序列化以完全無反射的方式在 BSON 之間編解碼對象。
R2DBC――Micronaut 框架提供瞭一個基於 Netty 的反應式非阻塞核心。結閤使用 Micronaut Netty 服務器和響應式數據庫連接 (Reactive Database Connectivity,R2DBC) 規範及數據庫實現,你可以開發齣無端到端阻塞的 SQL 應用程序。
Oracle Coherence――一個大規模分布式數據網格,Coherence 的特點是專門與 Micronaut Data 集成,可以輕鬆實現由 Coherence 集群提供支持的 Repository。關於 Micronaut 框架的所有不同的數據庫訪問選項可以單獨寫成一係列文章。不過好在已經有一些優秀的指南可參考:“使用 Micronaut Data JDBC 訪問數據庫”或“使用 Micronaut Data Hibernate/JPA 訪問數據庫”。
我個人喜歡 Micronaut Data JDBC,它是一個簡單的 JDBC 數據映射器。它是基於編譯時 Bean 自省,完全消除瞭持久化層的反射。
如果你在 Gradle 或 Maven 構建文件中配置瞭 Micronaut Data JDBC,就可以創建映射到數據庫錶、視圖或查詢結果的 Java 17 記錄對象。這與 JPA 不同,JPA 中的 Java 類和錶之間是一對一的映射,並通過關聯對模式進行建模。這些關聯引入瞭延遲加載等概念,而延遲加載往往會導緻性能問題 (比如臭名昭著的 N+1 查詢問題)。下麵是一個記錄類定義示例:
然後,你可以為實現瞭大多數應用程序邏輯的接口定義 Repository 邏輯。例如:
上麵的示例通過 CrudRepository 接口提供瞭完整的創建、讀取、更新和刪除操作 (CRUD)。它還使用查詢錶達式定義瞭自定義查詢。
如果你有更高級的用例,可以編寫自定義查詢、標準查詢,或者直接編寫 JDBC 邏輯來綁定結果。Micronaut Data JDBC 在完全不需要反射和運行時生成代理的情況下讓這些變得輕而易舉,沒有 JPA 中的那種狀態和會話同步概念,有助於保持應用程序的輕量級以及構建成 GraalVM 原生鏡像之後的齣色性能。
此外,每個 Repository 接口的檢查都發生在編譯時。這樣可以防止 Repository 方法查詢不存在的屬性或使用不支持的返迴類型,這在支持強大的動態特性的同時,維護瞭 Java 的類型安全。
構建原生可執行文件
Micronaut 框架的第一個版本是在 GraalVM 之前發布的。然後,這兩項偉大的技術之間産生瞭自然而然的協同作用,主要是因為 GraalVM 的原生鏡像組件可以很容易地將一個 Micronaut 應用程序轉換為一個原生可執行文件。
GraalVM 原生鏡像可以很好地支持 Java 反射、運行時代理和動態類加載。開發人員需要為原生鏡像提供必要的配置,說明在何時何地可以使用它們。但對於 Micronaut 框架就不需要提供這些聲明,因為 Micronaut 應用程序沒有在框架級彆使用這些技術!這使得 GraalVM 原生鏡像的提前編譯 (AOT) 分析變得更加簡單。
當然,如果你使用瞭依賴反射的第三方庫,則需要聲明。但是,你所使用的框架中的大多數東西都是無反射的。
Micronaut Gradle 和 Micronaut Maven 插件利用瞭 Oracle Labs 提供的 GraalVM 原生構建工具來簡化原生可執行文件的構建。因此,用 Gradle 構建一個原生可執行文件就是這麼簡單:
使用 Maven 是這樣:
這兩個命令都將在工具的構建目錄中為目標平台生成原生可執行文件。
運行原生可執行文件:
啓動時間減少到幾毫秒 (上麵的例子是 23 毫秒),內存消耗也顯著下降。有瞭這樣一個巨大的改進,就可以將 Micronaut 應用程序部署到內存限製有限或啓動速度非常關鍵的環境中 (例如,無服務器工作負載)。
需要注意的是,目前正在進一步研究通過由 Oracle Labs 開發的 Micronaut AOT 項目來實現更重大的改進。它在構建原生可執行文件之前會對字節碼進行額外的靜態分析,以優化和消除死代碼路徑,並將 YAML 轉換為 Java,避免在運行時使用 YAML 解析器,等等。
為雲而構建
除瞭原生鏡像,Micronaut 框架還支持許多不同的打包格式和部署目標,包括:
使用./gradlew assemble 或./mvnw package 構建的傳統 JAR 包。
使用./gradlew dockerBuild 或./mvnw package -Dpackaging=docker 構建的 Docker 鏡像。
使用./gradlew dockerBuildNative 或./mvnw package -Dpackaging=docker-native 構建的包含原生可執行文件(來自 GraalVM 原生鏡像)的 Docker 鏡像。
自定義 AWS Lambda 運行時,可以將 Micronaut 應用程序部署到無服務器平台。
與 Kubernetes 集成,可以簡化在 Kubernetes 集群中的部署。總的來說,Micronaut 框架提供的特性使其成為構建雲原生 Java 應用程序的最佳選擇,從分布式配置支持到集成服務發現,再到為 AWS、Google Cloud、Azure 和 Oracle Cloud 等雲供應商提供的公共抽象實現模塊。這些抽象確保你的應用程序可以在雲供應商之間保持可移植性。
總 結
Micronaut 框架為服務器端 Java 工作負載帶來瞭一股新鮮的空氣。它提供瞭一種創新的編譯時方法和特性,使其成為構建現代雲原生 Java 應用程序的最佳候選。
與 GraalVM 原生鏡像的緊密集成,以及與 Oracle Labs GraalVM 團隊的工作關係,意味著 Micronaut AOT 和 Micronaut 序列化 (Jackson Databind 的無反射替代方案) 等項目將繼續帶來重大創新。
Micronaut 框架有一個充滿活力的社區,提供瞭許多提高開發人員生産力的模塊,包括與數據庫技術集成的 Micronaut Data。
社區的反饋將繼續推動框架的發展。因此,如果你有任何反饋,請不要猶豫,通過 Micronaut 社區分享關於新功能和改進的想法。
作者簡介
Graeme Rocher 是幾個流行開源項目的創建者,包括 Grails 和 Micronaut,也是《Grails 權威指南》的閤著者。Graeme 目前是 Oracle 的架構師。Graeme 是 Java Champions 成員,並因其在開源方麵的重要貢獻於 2018 年榮獲由 Oracle 頒發的 Groundbreaker 奬項。
https://www.infoq.com/articles/native-java-micronaut/