簡介
JRE:Java Runtime Environment
JDK:Java Development Kit
JDK 是開發環境
JRE 是運行環境
JDK 包含了JRE
JRE 中包含虛擬機JVM
如果只需要運行Java 程序,那么可以只安裝JRE
HotSpot VM 是 JDK 默認內置的 JVM
Graal VM 在 HotSpot JVM 的基礎上,增加了一個增強的 JIT(Just-in-Time) 編譯器,即 Graal 編譯器,以及一個 language implementation framework (Truffle) 用於支持多種語言,使得 Graal VM 成為可以支持多種語言的虛擬機,既支持 Java、Scala、Groovy、Kotlin 等基於 Java 的語言,還支持 C、C++、Rust、JavaScript、Ruby、Python、R 等語言,並且支持不同語言互相調用
Graal 支持 AOT (Ahead of Time Compilation),即提前編譯,就是把程序直接編譯成二進制直接運行,提升了啟動速度,減少了內存使用
在雲原生微服務時代,容器化環境要求應用更輕量更快啟動,傳統的 SpringBoot 程序,將 Jar 包和 Java 環境打包進 image 需要 100 多 M 空間,啟動也偏慢
Quarkus 就是為了解決這個問題的,Quarkus 使用 Graal 構建應用
Quarkus 有以下特點
- 容器優先,啟動快,體積小
- 滿足 Kubernete 雲原生要求,比如 一份基准代碼多份部署/開發環境與生產環境等價/顯式聲明依賴關系/無狀態運行 等等
- 統一命令式與反應式編程
- 支持 native 模式,即編譯出來的可執行文件能直接運行,不需要 JVM
- 提高開發效率,比如支持實時編碼/統一配置 等
- 使用 Microprofile 規范構建微服務
下面講一個簡單的例子
創建項目
需要安裝設置
- Maven 3.8.1+
- JDK 11+
- JAVA_HOME 指向 JDK 11+
創建命令
apache-maven-3.8.4/bin/mvn io.quarkus:quarkus-maven-plugin:2.4.1.Final:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=quarkus-getting-started \
-DclassName="com.example.demo.resources.GreetingResource" \
-Dpath="/hello"
效果和使用 io.quarkus.platform 創建一樣,文件 pom.xml 的 dependencyManagement 用的都是 io.quarkus.platform:quarkus-bom 而不是用 io.quarkus:quarkus-bom,這兩個差不多,有少數 dependency 不一樣,並且 quarkus 版本是最新的 2.6.1.Final 而不是指定的 2.4.1.Final,不清楚是為什么
這里的 dependencyManagement 起着類似 parent 的作用,使得 dependencies 下的 dependency 可以不用指定版本,直接用 dependencyManagement 引用的 bom 里面的相同 dependency 的版本
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.6.1.Final</quarkus.platform.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
需要的話就手動把 bom 和 version 換了
初始的用於 src 代碼的 dependency 只有兩個
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
build-plugins 里面有個 quarkus 插件用於編譯、啟動調式 quarkus 應用
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
profiles 里面有個 native profile 幫助生成 native 程序 (就是可以直接運行的文件,不需要 java)
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
程序已經自動生成了一個 helloworld
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello RESTEasy";
}
}
自動生成的文件包括
quarkus-getting-started/
├── pom.xml
├── README.md
└── src
├── main
│ ├── docker
│ │ ├── Dockerfile.jvm
│ │ ├── Dockerfile.legacy-jar
│ │ ├── Dockerfile.native
│ │ └── Dockerfile.native-distroless
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ └── resources
│ │ └── GreetingResource.java
│ └── resources
│ ├── application.properties
│ └── META-INF
│ └── resources
│ └── index.html
└── test
└── java
└── com
└── example
└── demo
└── resources
├── GreetingResourceTest.java
└── NativeGreetingResourceIT.java
可以看到還包括 dockerfile、測試文件等
啟動調式代碼
可以看到 quarkus 是沒有 main application 啟動類的
可以通過命令啟動
mvn compile quarkus:dev
如果用 IDEA 可以到右側的 Maven 窗口找到對應項目下的 Plugins -> quarkus -> quarkus:dev 然后雙擊
(有時可能需要先點擊 Plugins -> compiler -> compiler:compile)
訪問 localhost:8080/hello 成功返回
Hello RESTEasy
嘗試修改代碼
@Path("/api/v1")
public class GreetingResource {
@Path("hello")
@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello() {
return "Hello Quarkus";
}
}
訪問 http://localhost:8080/api/v1/hello 成功返回
Hello Quarkus
從日志可以看到當重新訪問時 quarkus 會檢測到文件的改變並重啟
Restarting quarkus due to changes in GreetingResource.class.
所以 debug 的時候可以不用手動重啟程序,只要刷新頁面,或重新訪問,就可以
修改配置文件同樣不需要手動重啟
# application.properties
quarkus.http.port=8180
greeting.message=hello world
// GreetingResource.java
@Path("/api/v1")
public class GreetingResource {
@ConfigProperty(name = "greeting.message")
String message;
@Path("hello")
@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello() {
return message;
}
}
刷新頁面成功返回
hello world
從日志可以看到
File change detected: C:\Users\ezehlin\Desktop\IDEA_Project\quarkus-getting-started\src\main\resources\application.properties
Restarting quarkus due to changes in application.properties, GreetingResource.class.
但是端口的改變並沒有起作用,需要重新啟動才會改成 8180,不然還是用的 8080
如果要使用 yaml 配置文件需要添加依賴
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
application.yaml 和 application.propertis 可以同時存在,而且如果有相同配置項的話,是以 yaml 的為准
Quarkus 的所有可用配置項可以參考官網 https://quarkus.io/guides/all-config
測試
pom 文件里用於測試的兩個依賴
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
一個用於啟動 Quarkus 測試,一個用於測試 REST 請求 (也可以用其他 REST 測試工具)
為了和 junit5 配合,build-plugins 需要引入
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
測試類
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/api/v1/hello")
.then()
.statusCode(200)
.body(is("Hello Quarkus"));
}
}
@QuarkusTest 會啟動 Quarkus 應用,這里應該是 FT/IT 而不是 UT,做 UT 測試使用 mockito 就可以
測試時 server 和 client 都默認使用端口 8081
如果 test 目錄下沒有 resources 會使用 main 目錄下的 resources
打包運行:JVM
執行 mvn package 或在 IDEA 的右邊的 Maven 窗口雙擊 Lifecycle -> package
target 下會生成 quarkus-getting-started-1.0.0-SNAPSHOT.jar 包,但這不是可以直接運行的
target 下還生成了 quarkus-app 目錄,這個目錄下面有
app/
lib/
quarkus/
quarkus-app-dependencies.txt
quarkus-run.jar
然后可以通過 java 命令啟動 quarkus 應用
java -jar target/quarkus-app/quarkus-run.jar
如果要在其他地方使用,需要把整個 quarkus-app 目錄考過去
打包運行:Legacy-Jar
執行 mvn package 的時候添加參數指定 package 類型
mvn package -Dquarkus.package.type=legacy-jar
和 JVM 類型比,target 目錄下沒有了 quarkus-app 目錄
而多了一個 quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
通過 java 命令啟動
java -jar ./quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
如果要在其他地方使用,除了這個 jar 包,還需要把 target/lib 目錄拷過去
打包運行:Native
執行 mvn package 的時候添加參數指定 package 類型
mvn package -Dquarkus.package.type=native
或是指定 pom 文件定義好的 profile
mvn package -Pnative
可以看到 target 目錄下生成了一個 quarkus-getting-started-1.0.0-SNAPSHOT-runner 文件
可以直接運行這個命令啟動
./quarkus-getting-started-1.0.0-SNAPSHOT-runner
這種模式不需要安裝 java 環境
如果要在其他地方使用,只需要拷貝這個文件過去就可以
編譯 native 模式需要安裝 graalvm
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz
export GRAALVM_HOME=/home/lin/quarkus/graalvm-ce-java11-22.0.0.2
${GRAALVM_HOME}/bin/gu install native-image
如果不想安裝 graalvm 可以通過 Docker 編譯
mvn package -Pnative -Dquarkus.native.container-build=true
這樣會啟動一個有 graalvm 環境的容器,然后在里面做編譯,編譯結果會放到容器外面
(似乎可以不用指定,系統如果檢查不到 graalvm 環境就會自動使用容器編譯)
編譯測試 native 模式感覺花的時間多了很多
Dockerfile
生成的項目里面有 4 個 Dockerfile
Dockerfile.jvm
Dockerfile.legacy-jar
Dockerfile.native
Dockerfile.native-distroless
分別對應前面提到的幾種打包運行機制
native-distroless 和 native 只是基礎鏡像不同
distroless 鏡像只包含應用程序以及其運行時所需要的依賴。不包含包管理器、shells 或者其他程序
官網說法: Distroless image support is experimental
測試: native
pom 文件的 native profile 下有 plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
native.image.path 指定了 native-image 的路徑,就是上面生成的 quarkus-getting-started-1.0.0-SNAPSHOT-runner 路徑
測試代碼
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
public class NativeGreetingResourceIT extends GreetingResourceTest {
// Execute the same tests but in native mode.
}
可以看到和前面的 GreetingResourceTest 是一樣的,只是換成了 @NativeImageTest 注解
執行命令驗證 mvn verify -Pnative
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.demo.resources.NativeGreetingResourceIT
Executing "/home/lin/quarkus/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-runner -Dquarkus.http.port=8081 -Dquarkus.http.ssl-port=8444 -Dtest.url=http://localhost:8081 -Dquarkus.log.file.path=/home/lin/quarkus/quarkus-getting-started/target/quarkus.log -Dquarkus.log.file.enable=true"
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-01-29 17:34:27,262 INFO [io.quarkus] (main) quarkus-getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 2.6.3.Final) started in 0.030s. Listening on: http://0.0.0.0:8081
2022-01-29 17:34:27,262 INFO [io.quarkus] (main) Profile prod activated.
2022-01-29 17:34:27,262 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, smallrye-context-propagation, vertx]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.108 s - in com.example.demo.resources.NativeGreetingResourceIT
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
會啟動 native.image.path 指定的可執行文件,然后做測試
同樣需要有 graalvm 環境或是通過 docker build (哪怕 runner 已經存在也會執行)
如果希望直接測試已經存的 runner 而不重新 build,可以執行
mvn test-compile failsafe:integration-test
如果需要重新編譯的話,那么還是比較花時間的