03.05 給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

以下內容均選自“實驗樓”新課《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 終端中執行以下命令:

<code>mn create-app example.micronaut.complete/<code>
  • mn 用於調用 Micronaut 的 CLI。
  • create-app 即表明要創建一個 Micronaut 程序。默認情況下,create-app 會創建一個 Java Micronaut 程序,並使用 Gradle 構建系統。但你也可以使用其他的構建工具(如:Maven)和語言(如:Groovy 和 Kotlin)。
  • example.micronaut.complete 作為參數,指定了程序的默認包名和程序名,最後一個 . 號前面的內容將作為包名(此處即:example.micronaut),後面的內容將作為文件夾名(此處即:complete)。

執行後的結果如下:

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

注意:以後的實驗中,如無特殊說明,都將在 /home/project 目錄下創建程序。

Application

Application.java 文件被用於使用 Gradle 或部署時運行程序,你也可以通過直接執行該類的 main() 函數來直接運行程序。其代碼如下:

<code>package example.micronaut;

import io.micronaut.runtime.Micronaut;

public class Application {

public static void main(String[] args) {
Micronaut.run(Application.class);
}
}/<code>

▲編寫代碼

Controller

為了創建一個能夠響應並返回 "Hello World" 的微服務,首先我們需要創建一個 Controller。

在 src/main/java/example/micronaut 目錄下創建 HelloController.java 文件,編寫代碼如下:

<code>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"; // ④
}
}/<code>

為了便於查看,我們在程序中用數字來標註,然後在下方給予解釋,序號一一對應。

  1. 在這裡,我們使用 @Controller 註解定義了一個 Controller,並把它映射到 /hello 路徑。
  2. @Get 註解用於將 index() 方法和所有 HTTP GET 的請求進行映射。
  3. 在默認情況下,Micronaut 程序的響應會使用 application/json 作為 Content-Type。這裡我們返回一個字符串類型的數據,不是 JSON,所以設置為 text/plain。
  4. 返回字符串 "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 文件,編寫代碼如下:

<code>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);
}
}/<string>/<code>
  1. 對類使用 @MicronautTest 註解,這樣 Micronaut 會初始化 Application Context 和 Embedded Server。
  2. 注射 RxHttpClient 的 bean,用於執行 HTTP 請求來訪問 Controller。
  3. 使用 Micronaut 提供的 API 創建 HTTP 請求。

▲運行程序

測試程序

在運行程序之前,最好的做法是先進行測試。在實驗樓 WebIDE 終端中執行以下命令進行測試。

<code># 進入項目的目錄
cd /home/project/complete
# 測試
./gradlew test/<code>

執行的結果如下所示。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

運行程序

在實驗樓 WebIDE 終端中使用以下命令可以讓程序在 8080 端口運行。

<code>./gradlew run/<code>

當控制檯顯示內容如下時,表明程序已經處於運行狀態中。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

接著,打開 Web 服務,在地址欄末尾添加 /hello 來訪問我們的 /hello API。結果如下。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

如果要停止程序運行,按下 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 文件,追加以下依賴。

<code>dependencies {
annotationProcessor "io.micronaut:micronaut-graal"
}/<code>
給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

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 的依賴:

<code>dependencies {
compileOnly "org.graalvm.nativeimage:svm:19.3.0"
}/<code>
給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

為了使構建鏡像更加簡潔,我們需要創建一個 native-image.properties 文件。標準做法是使用 sra/main/resources/META-INF/native-image 文件夾並在該文件夾下創建一個遵循 Maven 座標來描述程序的文件夾。例如,我們的項目名設為 micronautguide,則應創建 example.micronaut/micronautguide 文件夾。下面通過命令行的模式來進行創建。

<code># 進入項目目錄
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/<code>

然後,在 native-image.properties 文件中編寫以下內容。

<code>Args = -H:IncludeResources=logback.xml|application.yml \\
-H:Name=micronautguide \\
-H:Class=example.micronaut.Application/<code>
  • -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 終端中執行以下命令。

<code>./gradlew assemble/<code>

執行後的結果如下。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

然後開始生成 Native Image,生成鏡像的命令如下。

<code>native-image --no-server -cp build/libs/complete-0.1-all.jar/<code>
  • -no-server 選項表明不使用基於 server 的鏡像來構建。

重要提示

構建 Native Image 的開銷較大,由於實驗樓的環境限制,標準環境下會因內存不足而失敗。所以我們建議跳過構建鏡像的步驟,下載我們提前構建的 Native Image。同學可以在自己電腦上進行嘗試。

在實驗樓 WebIDE 終端中執行以下命令下載構建好的鏡像。

<code># 進入項目目錄
cd /home/project/complete
# 下載鏡像
wget https://labfile.oss.aliyuncs.com/courses/1511/micronautguide
# 為文件添加可執行權限
chmod +x ./micronautguide/<code>

這裡提供一下生成鏡像時的控制檯輸出截圖,可以看到在 1 核 4 G 的環境中花了 20 分鐘的時間。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

最後,讓我們來體驗一下生成的鏡像的優勢。在實驗樓 WebIDE 中執行以下命令運行程序。

<code>./micronautguide -Xmx68m/<code>

執行結果如下。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序

可以看到程序比起之前啟動的速度明顯提升了不少。新建一個控制檯(菜單欄 -> Terminal -> New Terminal),執行以下命令來訪問 /hello API 接口。

<code>curl localhost:8080/hello/<code> 

執行結果如下。

給新手看的Micronaut入門教程,10 分鐘寫出一個Micronaut程序



你的第一個程序就完成啦,想查看本節實驗的源碼並繼續學習後面的內容,可訪問“實驗樓”官網,搜索課程《Micronaut 入門實戰:基於 JVM 的微服務框架》。


分享到:


相關文章: