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

如果需要重新编译的话,那么还是比较花时间的





posted @   moon~light  阅读(1351)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-01-16 5G 网络与物联网
点击右上角即可分享
微信分享提示