以下內容均選自 Micronaut 入門實戰:基於 JVM 的微服務框架 。
Micronaut是什么?
Micronaut 是一個現代化的基於 JVM 的全棧框架,用於構建模塊化且易於測試的微服務或無服務程序。
同時,Micronaut 使用 Netty,並且對響應式編程提供一流的支持。對於 JVM 領域的全棧框架來說,Micronaut 是一個非常有前途的新成員。
Micronaut 旨在提供構建微服務所需要的一切工具,包含:
- 依賴注入(DI)和控制翻轉(IoC)。
- 合理的默認值和自動配置。
- 配置及配置共享。
- 服務發現。
- HTTP 路由。
- 具有負載均衡的 HTTP 客戶端。
同時,Micronaut 也致力於避免像 Spring、Spring Boot 和 Grails 中的弊端,通過:
- 快速啟動。
- 減少內存占用。
- 極少的反射使用。
- 極少的代理使用。
- 簡單的單元測試。
在以前,像 Spring 和 Grails 這些框架並不是被設計來在 server-less、安卓 Apps 或低內存占用的場景中運行。相反,Micronaut 則是為此而生。
Micronaut 通過使用 Java 的 annotation processor 來實現以上功能,annotation processor 可以在任何支持其的 JVM 上使用,包括在使用 Netty 構建的 HTTP Server 和 Client 上。
為了提供和 Spring 以及 Grails 相似的編程模型,這些 annotation processor 預編譯了必要的元數據(Metadata)來進行依賴注入、定義 AOP 代理以及配置你的應用程序,使其能夠在微服務環境中運行。
創建第一個 Micronaut 程序
接下來將要創建我們的第一個 Micronaut 程序,內容包括提供一個 /hello API 接口以及相應的測試類。
然后,使用 GraalVM 提供的 native image 將程序構建為一個可運行的二進制文件。
▲創建程序
Micronaut 提供了一個 CLI(Command Line Interface,命令行接口)讓我們可以方便快速地創建一個 Micronaut 程序。這個 CLI 包含了用於生成指定類型項目的命令,可以選擇項目的構建工具、測試框架甚至是程序使用的語言。同時,它也提供了生成代碼(如:Controller、Client Interface 和 Serverless Functions)的命令。詳情可參考官網文檔 Micronaut CLI。
下面我們來使用 Micronaut CLI 來創建程序,在實驗樓 WebIDE 終端中執行以下命令:
mn create-app example.micronaut.complete
- mn 用於調用 Micronaut 的 CLI。
- create-app 即表明要創建一個 Micronaut 程序。默認情況下,create-app 會創建一個 Java Micronaut 程序,並使用 Gradle 構建系統。但你也可以使用其他的構建工具(如:Maven)和語言(如:Groovy 和 Kotlin)。
- example.micronaut.complete 作為參數,指定了程序的默認包名和程序名,最后一個 . 號前面的內容將作為包名(此處即:example.micronaut),后面的內容將作為文件夾名(此處即:complete)。
執行后的結果如下:
注意:以后的實驗中,如無特殊說明,都將在 /home/project 目錄下創建程序。
Application
Application.java 文件被用於使用 Gradle 或部署時運行程序,你也可以通過直接執行該類的 main() 函數來直接運行程序。其代碼如下:
package example.micronaut;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class);
}
}
▲編寫代碼
Controller
為了創建一個能夠響應並返回 "Hello World" 的微服務,首先我們需要創建一個 Controller。
在 src/main/java/example/micronaut 目錄下創建 HelloController.java 文件,編寫代碼如下:
package example.micronaut;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
@Controller("/hello") // ①
public class HelloController {
@Get // ②
@Produces(MediaType.TEXT_PLAIN) // ③
public String index() {
return "Hello World"; // ④
}
}
為了便於查看,我們在程序中用數字來標注,然后在下方給予解釋,序號一一對應。
- 在這里,我們使用 @Controller 注解定義了一個 Controller,並把它映射到 /hello 路徑。
- @Get 注解用於將 index() 方法和所有 HTTP GET 的請求進行映射。
- 在默認情況下,Micronaut 程序的響應會使用 application/json 作為 Content-Type。這里我們返回一個字符串類型的數據,不是 JSON,所以設置為 text/plain。
- 返回字符串 "Hello World" 作為請求響應結果。
注:后續實驗中,如無特殊說明的情況下,在編寫程序時,默認使用 src/main/java/example/micronaut 作為目錄。
如:創建 HelloController.java 文件。
即:在 src/main/java/example/micronaut 目錄下創建 HelloController.java 文件。
Test
接着我們創建一個 Junit 的測試來驗證當我們對 /hello 進行一次 GET 請求時,是否會得到 Hello World 作為響應。
在 src/test/java/example/micronaut 目錄下創建 HelloControllerTest.java 文件,編寫代碼如下:
package example.micronaut;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
@MicronautTest // ①
public class HelloControllerTest {
@Inject
@Client("/")
RxHttpClient client; // ②
@Test
public void testHello() {
HttpRequest<String> request = HttpRequest.GET("/hello"); // ③
String body = client.toBlocking().retrieve(request);
assertNotNull(body);
assertEquals("Hello World", body);
}
}
- 對類使用 @MicronautTest 注解,這樣 Micronaut 會初始化 Application Context 和 Embedded Server。
- 注射 RxHttpClient 的 bean,用於執行 HTTP 請求來訪問 Controller。
- 使用 Micronaut 提供的 API 創建 HTTP 請求。
▲運行程序
測試程序
在運行程序之前,最好的做法是先進行測試。在實驗樓 WebIDE 終端中執行以下命令進行測試。
# 進入項目的目錄
cd /home/project/complete
# 測試
./gradlew test
執行的結果如下所示。
運行程序
在實驗樓 WebIDE 終端中使用以下命令可以讓程序在 8080 端口運行。
./gradlew run
當控制台顯示內容如下時,表明程序已經處於運行狀態中。
接着,打開 Web 服務,在地址欄末尾添加 /hello 來訪問我們的 /hello API。結果如下。
如果要停止程序運行,按下 Ctrl + C 即可。
▲使用 GraalVM 生成 Native Image
我們將使用 GraalVM,一個多語言可嵌入的虛擬機,來為我們的程序生成一個 Native Image(本地鏡像)。
Native Images 用到了 GraalVM 的 ahead-of-time 技術為基於 JVM 的應用改善啟動時間並減少內存占用。
▲Micronaut + GraalVM 的變化
Micronaut 本身不依賴反射或動態類加載,所以可以很好地適配 GraalVM Native。
首先,添加 Micronaut Graal 的注解處理器來用於生成 native-image 工具要使用的 reflection-config.json 元數據。
打開項目的 build.gradle 文件,追加以下依賴。
dependencies {
annotationProcessor "io.micronaut:micronaut-graal"
}
GraalVM Native Image 允許我們預編譯(ahead-of-time compile)Java 代碼來生成一個叫做本地鏡像(Native Image)的獨立可執行文件。這個可執行文件包含了程序本身、庫和 JDK,且不運行在 Java 虛擬機上,但在另一個叫做 SVM(Substrate VM) 的虛擬機中包含了必要的組件,如內存管理和線程調度。SVM 是運行時組件的一個統稱,這些組件包括 deoptimizer_,_garbage collector 和 thread scheduling 等等。這使得生成的程序會比使用 Java 虛擬機時擁有更快的啟動速度以及更低的內存占用。
我們需要在 build.gradle 中添加一個 SVM 的依賴:
dependencies {
compileOnly "org.graalvm.nativeimage:svm:19.3.0"
}
為了使構建鏡像更加簡潔,我們需要創建一個 native-image.properties 文件。標准做法是使用 sra/main/resources/META-INF/native-image 文件夾並在該文件夾下創建一個遵循 Maven 坐標來描述程序的文件夾。例如,我們的項目名設為 micronautguide,則應創建 example.micronaut/micronautguide 文件夾。下面通過命令行的模式來進行創建。
# 進入項目目錄
cd /home/project/complete
# 創建文件夾
mkdir -p src/main/resources/META-INF/native-image/example.micronaut/micronautguide
# 創建文件
touch src/main/resources/META-INF/native-image/example.micronaut/micronautguide/native-image.properties
然后,在 native-image.properties 文件中編寫以下內容。
Args = -H:IncludeResources=logback.xml|application.yml \
-H:Name=micronautguide \
-H:Class=example.micronaut.Application
- -H:IncludeResources:參數允許我們設置包括哪些靜態資源,可以使用正則表達式。這里為 logback.xml 和 application.yml。
- -H:Name:參與指定了生成的 Native Image 的名字,這里為 micronautguide。
- -H:Class:參數指定了程序的入口(定義了 main() 方法的類)。這里為 example.micronaut.Application,即 Application.java 文件中的 main() 方法。
▲生成 Native Image
為了生成 Native Image,我們需要先生成程序的 FAT JAR。
在實驗樓 WebIDE 終端中執行以下命令。
./gradlew assemble
執行后的結果如下。
然后開始生成 Native Image,生成鏡像的命令如下。
native-image --no-server -cp build/libs/complete-0.1-all.jar
- -no-server 選項表明不使用基於 server 的鏡像來構建。
重要提示
構建 Native Image 的開銷較大,由於實驗樓的環境限制,標准環境下會因內存不足而失敗。所以我們建議跳過構建鏡像的步驟,下載我們提前構建的 Native Image。同學可以在自己電腦上進行嘗試。
在實驗樓 WebIDE 終端中執行以下命令下載構建好的鏡像。
# 進入項目目錄
cd /home/project/complete
# 下載鏡像
wget https://labfile.oss.aliyuncs.com/courses/1511/micronautguide
# 為文件添加可執行權限
chmod +x ./micronautguide
這里提供一下生成鏡像時的控制台輸出截圖,可以看到在 1 核 4 G 的環境中花了 20 分鍾的時間。
最后,讓我們來體驗一下生成的鏡像的優勢。在實驗樓 WebIDE 中執行以下命令運行程序。
./micronautguide -Xmx68m
執行結果如下。
可以看到程序比起之前啟動的速度明顯提升了不少。新建一個控制台(菜單欄 -> Terminal -> New Terminal),執行以下命令來訪問 /hello API 接口。
curl localhost:8080/hello
執行結果如下。
你的第一個程序就完成啦,可在 Micronaut 入門實戰:基於 JVM 的微服務框架 查看本節實驗的源碼並繼續學習后面的內容。