创建一个Quarkus应用程序

说明:本文基本翻译自 Quarkus 官方指南的《Be Guided Through First Application》 一文,本文仅在其基础上进行了一定的补充说明和裁剪。

Quarkus简介

Quarkus 是一个为 Java 虚拟机(JVM)和原生编译而设计的全堆栈 Kubernetes 原生Java 框架,用于专门针对容器优化 Java,并使其成为无服务器、云和 Kubernetes 环境的高效平台。

虽然开源时间较短,但是生态方面也已经达到可用的状态,自身包含扩展框架,已经支持像 Netty、Undertow、Hibernate、JWT 等框架,足以用于开发企业级应用,用户也可以基于扩展框架自行扩展。

Quarkus 除了支持 jvm 的运行模式外,还支持 native 模式。native 模式类似于 C/C++,需要预先编译生成可执行文件。由于不需要 jvm,所以可以做到毫秒级别的启动,而且启动所需的内存也非常少。因此native模式非常适合 serverless 架构的实现。

开发环境

  • 一个IDE
  • 安装了JAVA_HOME正确配置的JDK 8或11+ (这里建议使用GraalVM 21+ 的java11版本,便于后续将quarkus项目打包为native程序,本文使用的环境为
    GraalVM CE 21.1.0 (build 11.0.11+8-jvmci-21.1-b05)
  • Apache Maven 3.6.2以上。如果您安装了多个JDK,不确定Maven在运行时是否会选择预期的Java,错误的Java版本最终可能会导致意外的结果,甚至无法进行打包和编译。您可以通过运行mvn --version来验证使用哪个JDK Maven。

创建项目

创建新的Quarkus项目的最简单方法是打开一个终端并运行以下命令:

对于Linux和MacOS用户

1
2
3
4
5
6
mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=getting-started \
-DclassName="org.acme.getting.started.GreetingResource" \
-Dpath="/hello"
cd getting-started

对于Windows用户

  • 如果使用cmd,(请不要使用反斜杠 \ )
    1
    mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started -DclassName="org.acme.getting.started.GreetingResource" -Dpath="/hello"
  • 如果使用Powershell,则将-D参数用双引号引起来
    1
    mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create "-DprojectGroupId=org.acme" "-DprojectArtifactId=getting-started" "-DclassName=org.acme.getting.started.GreetingResource" "-Dpath=/hello"
    命令会自动生成以下内容 ./getting-started:
  • Maven项目的结构文件和目录
  • org.acme.getting.started.GreetingResource文件中暴露出的Web资源路径 /hello
  • 相关的单元测试文件和目录
  • http://localhost:8080启动应用程序后可访问的登录页面
  • src/main/docker 目录下存放着 native 和 jvm 模式下的 Dockerfile 文件示例
  • 应用程序配置文件

项目生成后,查看pom.xml文件,你可以看到Quarkus导入的控件(依赖项)清单,允许您忽略不同Quarkus依赖项上的版本。

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-universe-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

JAX-RS资源
在项目创建期间,已创建src/main/java/org/acme/getting/started/GreetingResource.java文件并包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.acme.getting.started;

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";
}
}

这就是编写的web接口的代码逻辑。这是一个非常简单的REST端点,对/hello发起GET请求你将得到返回值“hello”。

使用Quarkus时,开发人员并不需要创建Application启动类。当然,如果你想写的话它也是受支持的,但不是必需的。另外,默认情况下仅会创建资源的一个实例,而不为每个请求创建一个。不过你可以通过配置不同的*Scoped注解(ApplicationScoped,RequestScoped等)来实现不同的资源作用域。

运行应用程序

程序生成后就可以运行了,运行命令为./mvnw compile quarkus:dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ./mvnw compile quarkus:dev
[INFO] --------------------< org.acme:getting-started >---------------------
[INFO] Building getting-started 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ getting-started ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/starksm/Dev/JBoss/Quarkus/starksm64-quarkus-quickstarts/getting-started/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ getting-started ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /Users/starksm/Dev/JBoss/Quarkus/starksm64-quarkus-quickstarts/getting-started/target/classes
[INFO]
[INFO] --- quarkus-maven-plugin:<version>:dev (default-cli) @ getting-started ---
Listening for transport dt_socket at address: 5005
2019-02-28 17:05:22,347 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-02-28 17:05:22,635 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 288ms
2019-02-28 17:05:22,770 INFO [io.quarkus] (main) Quarkus started in 0.668s. Listening on: http://localhost:8080
2019-02-28 17:05:22,771 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]

启动后,您可以通过curl尝试请求提供的端点:

1
2
$ curl -w "\n" http://localhost:8080/hello
hello

点击按键CTRL+C停止该应用程序

使用注入

Quarkus中的依赖注入基于ArC,ArC是为Quarkus架构量身定制的基于CDI的依赖注入解决方案。如果您不熟悉CDI,建议您阅读CDI简介指南。

Quarkus仅实现CDI功能的一个子集,并具有非标准功能和特定的APIS,您可以在Contexts and Dependency Injection指南中了解有关它的更多信息。

通过引入依赖项quarkus-resteasy可以很方便的使用ArC:

1
2
3
4
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

现在尝试在应用程序并添加一个 companion bean。创建src/main/java/org/acme/getting/started/GreetingService.java文件并编写以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
package org.acme.getting.started;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

public String greeting(String name) {
return "hello " + name;
}

}

编辑GreetingResource类将GreetingService注入,并使用它创建一个新的端点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.acme.getting.started;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.jaxrs.PathParam;

@Path("/hello")
public class GreetingResource {

@Inject
GreetingService service;

@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam String name) {
return service.greeting(name);
}

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}

再次启动程序./mvnw compile quarkus:dev。检查端点是否按预期返回了内容:

1
2
$ curl -w "\n" http://localhost:8080/hello/greeting/quarkus
hello quarkus

扩展模块

quarkus:dev在开发模式下运行Quarkus。这样可以通过后台编译实现热部署,这意味着当您修改Java文件或资源文件并刷新浏览器时,这些更改将自动生效。这对于资源文件(如配置属性文件)也适用。刷新浏览器会触发对工作区的扫描,如果检测到任何更改,将重新编译Java文件并重新部署应用程序;然后,重新部署的应用程序将为您的请求提供服务。如果编译或部署有任何问题,则会显示错误页面。

这还将在port上侦听调试器 5005。如果要在运行之前等待调试器附加,可以-Dsuspend在命令行中传递。如果您根本不想使用调试器,则可以使用-Ddebug=false

测试

好的,到目前为止,一切都很好,但是为了以防万一,通过一些测试会更好。

在生成的pom.xml文件中,您可以看到2个测试依赖项:

1
2
3
4
5
6
7
8
9
10
<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支持Junit 5测试。但使用时必须设置Surefire Maven插件的版本,因为默认版本不支持Junit 5:

1
2
3
4
5
6
7
8
9
10
<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>

我们还设置了java.util.logging系统属性,以确保测试将使用正确的logmanager,并且maven.home确保从${maven.home}/conf/settings.xml应用了自定义配置(如果有)。

生成的项目包含一个简单的测试。创建src/test/java/org/acme/getting/started/GreetingResourceTest.java文件并编写以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package org.acme.getting.started;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import java.util.UUID;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

@Test // 通过使用运行QuarkusTest程序,您可以指示JUnit在测试之前启动应用程序。
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200) // 检查HTTP响应状态代码和内容
.body(is("hello"));
}

@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}

}

您可以使用Maven运行这些测试程序:

1
./mvnw test

您也可以直接从IDE运行测试(确保首先停止了应用程序)。

打包并运行应用程序

该应用程序使用打包./mvnw package。它产生以下几个输出/target:

  • getting-started-1.0.0-SNAPSHOT.jar-仅包含项目的类和资源,这是Maven构建产生的常规工件-它不是可运行的jar;
  • quarkus-app包含quarkus-run.jarjar文件的目录-是可执行jar。请注意,这不是über-jar,因为依赖项已复制到的子目录中quarkus-app/lib/。
  • 您可以使用以下命令运行该应用程序:
    1
    java -jar target/quarkus-app/quarkus-run.jar

    如果要将应用程序部署到某个地方(通常在容器中),则需要部署整个quarkus-app目录。

在运行该应用程序之前,请不要忘记停止热重载模式(点击CTRL+C),否则您将遇到端口冲突。

打包成Native

如果你希望将项目打包成native程序,享受快速启动的特性,你可以通过配置 pom.xml 文件增加对于 native 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>${maven.compiler.parameters}</parameters>
</configuration>
</plugin>
<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>
</plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<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>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>

配置完成后,你可以使用如下命令进行打包:

1
./mvnw package -Pnative

打包的前提是你需要将mvn使用的Java版本设置为GraalVM 21+,具体版本文章开头已进行了说明。

本文所使用的版本为 GraalVM CE 21.1.0 (build 11.0.11+8-jvmci-21.1-b05)

打包完成后会在 /target 路径下生成一个可执行文件:getting-started-reactive-1.0.0-SNAPSHOT-runner,该文件即为我们需要的目标文件,执行命令./getting-started-reactive-1.0.0-SNAPSHOT-runner 即可完成服务的启动。

1
2
3
4
5
6
7
8
$ ./getting-started-reactive-1.0.0-SNAPSHOT-runner
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-05-27 10:41:39,559 INFO [io.quarkus] (main) getting-started-reactive 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.4.Final) started in 0.019s. Listening on: http://0.0.0.0:8080
2021-05-27 10:41:39,862 INFO [io.quarkus] (main) Profile prod activated.
2021-05-27 10:41:39,862 INFO [io.quarkus] (main) Installed features: [cdi, mutiny, resteasy-reactive, smallrye-context-propagation]

点击按键CTRL+C停止该应用程序

后续

致此,你已经可以使用 Quarkus 进行开发部署了,当然还有更多的内容需要学习。如果想了解更多的使用方法,可以通过 Quarkus官网指南 继续进行学习。


最后,感谢你的阅读,愿你的人生了无Bug。

------ 本文结束------