Quarkus 入門




簡介

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

如果需要重新編譯的話,那么還是比較花時間的






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM