编辑此页面

在 Quarkus 应用中使用 Eclipse Vert.x API

Vert.x 是一个用于构建响应式应用的工具集。正如 Quarkus 响应式架构中所述,Quarkus 在底层使用了 Vert.x。

Quarkus Reactive Core

Quarkus 应用可以访问并使用 Vert.x API。

本指南将介绍如何使用以下方式构建 Quarkus 应用:

  • Vert.x 的托管实例

  • Vert.x 事件总线

  • Vert.x Web Client

这是一篇入门指南。 Vert.x 参考指南涵盖了更多高级功能,例如 verticles 和原生传输。

架构

我们将构建一个简单的应用,公开四个 HTTP 端点:

  1. /vertx/lorem 返回一个小文件的内容

  2. /vertx/book 返回一个大文件(一本书)的内容

  3. /vertx/hello 使用 Vert.x 事件总线生成响应

  4. /vertx/web 使用 Vert.x Web Client 从 Wikipedia 检索数据

Architecture of the Vert.x guide

解决方案

我们建议您按照以下各节的说明逐步创建应用程序。当然,您也可以直接跳转到已完成的示例。

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 存档

解决方案位于 vertx-quickstart 目录中。

Mutiny

本指南使用 Mutiny API。如果您不熟悉 Mutiny,请查看 Mutiny - 一个直观的响应式编程库

引导应用

点击 此链接 来配置您的应用。它已为您选择了一些扩展:

  • rest-jackson,它也带来了 rest。我们将使用它来公开我们的 HTTP 端点。

  • vertx,它提供了对底层托管 Vert.x 的访问。

点击 生成应用 按钮,下载 zip 文件并解压缩。然后,在您喜欢的 IDE 中打开项目。

如果您打开生成的构建文件,可以看到已选择的扩展:

pom.xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-vertx</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest-jackson")
implementation("io.quarkus:quarkus-vertx")

在修改构建文件时,请添加以下依赖项:

pom.xml
<dependency>
  <groupId>io.smallrye.reactive</groupId>
  <artifactId>smallrye-mutiny-vertx-web-client</artifactId>
</dependency>
build.gradle
implementation("io.smallrye.reactive:smallrye-mutiny-vertx-web-client")

此依赖项提供了 Vert.x Web Client,我们将使用它来实现 /web 端点。

访问托管的 Vert.x 实例

创建 src/main/java/org/acme/VertxResource.java 文件。它将包含我们的 HTTP 端点。

在此文件中,复制以下代码:

package org.acme;

import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx;

import java.nio.charset.StandardCharsets;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/vertx")                        (1)
public class VertxResource {

    private final Vertx vertx;

    @Inject                             (2)
    public VertxResource(Vertx vertx) { (3)
        this.vertx = vertx;             (4)
    }
}
1 声明根 HTTP 路径。
2 我们使用构造函数注入来接收托管的 Vert.x 实例。字段注入也可以。
3 将托管的 Vert.x 实例作为构造函数参数接收。
4 将托管的 Vert.x 实例存储到字段中。

有了这些,我们就可以开始实现端点了。

使用 Vert.x 核心 API

注入的 Vert.x 实例提供了一系列可供使用的 API。在本节中,我们将使用 Vert.x 文件系统。它提供了一个非阻塞 API 来访问文件。

src/main/resources 目录下,创建一个 lorem.txt 文件,内容如下:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

然后,在 VertxResource 文件中添加以下方法:

@GET                                                                                   (1)
@Path("/lorem")
public Uni<String> readShortFile() {                                                   (2)
    return vertx.fileSystem().readFile("lorem.txt")                                    (3)
            .onItem().transform(content -> content.toString(StandardCharsets.UTF_8));  (4)
}
1 此端点处理路径 /lorem 上的 HTTP GET 请求(因此完整路径将是 vertx/lorem)。
2 由于 Vert.x API 是异步的,我们的方法返回一个 Uni。当 Uni 代表的异步操作完成时,内容将被写入 HTTP 响应。
3 我们使用 Vert.x 文件系统 API 读取创建的文件。
4 文件读取完成后,内容将存储在内存缓冲区中。我们将此缓冲区转换为 String。

在终端中,导航到项目根目录并运行:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

在另一个终端中,运行

> curl https://:8080/vertx/lorem

您应该会在控制台中看到文件内容打印出来。

Quarkus 提供了其他方式来提供静态文件。这是为指南制作的一个示例。

使用 Vert.x 流能力

读取文件并将内容存储在内存中适用于小文件,但不适用于大文件。在本节中,我们将了解如何使用 Vert.x 的流能力。

首先,下载 《战争与和平》 并将其存储在 src/main/resources/book.txt。这是一个 3.2 MB 的文件,虽然不算巨大,但足以说明流的用途。这一次,我们不会将文件的内容累积在内存中然后一次性写入,而是逐块读取这些块并将它们逐个写入 HTTP 响应。

因此,您的项目中应该有以下文件:

.
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
├── src
│  └── main
│     ├── docker
│     │  ├── ...
│     ├── java
│     │  └── org
│     │     └── acme
│     │        └── VertxResource.java
│     └── resources
│        ├── application.properties
│        ├── book.txt
│        └── lorem.txt

将以下导入添加到 src/main/java/org/acme/VertxResource.java 文件中:

import io.smallrye.mutiny.Multi;
import io.vertx.core.file.OpenOptions;

将以下方法添加到 VertxResource 类中:

@GET
@Path("/book")
public Multi<String> readLargeFile() {                                               (1)
    return vertx.fileSystem().open("book.txt",                                       (2)
                    new OpenOptions().setRead(true)
            )
            .onItem().transformToMulti(file -> file.toMulti())                       (3)
            .onItem().transform(content -> content.toString(StandardCharsets.UTF_8) (4)
                    + "\n------------\n");                                           (5)
}
1 这次我们返回一个 Multi,因为我们想要流式传输块。
2 我们使用 open 方法打开文件。它返回一个 Uni<AsyncFile>
3 当文件打开后,我们获得一个 Multi,其中包含块。
4 对于每个块,我们生成一个 String。
5 为了在响应中直观地看到块,我们附加一个分隔符。

然后,在终端中运行:

> curl https://:8080/vertx/book

它应该会检索到书籍内容。在输出中,您应该会看到类似以下的分隔符:

...
The little princess had also left the tea table and followed Hélène.

“Wait a moment, I’ll get my work.... Now then, what
------------
 are you
thinking of?” she went on, turning to Prince Hippolyte. “Fetch me my
workbag.”
...

使用事件总线

Vert.x 的核心功能之一是 事件总线。它为您的应用程序提供了一个基于消息的骨干。因此,您可以让不同的组件通过异步消息传递进行交互,从而解耦您的组件。您可以将消息发送给单个消费者,或分发给多个消费者,或实现请求-回复交互,即您发送一条消息(请求)并期望得到一个回复。这就是我们将在本节中使用的。我们的 VertxResource 将发送一条包含名称的消息到 greetings 地址。另一个组件将接收消息并生成 "hello $name" 的响应。VertxResource 将接收响应并将其作为 HTTP 响应返回。

因此,首先,将以下导入添加到 src/main/java/org/acme/VertxResource.java 文件中:

import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.ws.rs.QueryParam;

接下来,让我们扩展我们的 VertxResource 类,添加以下代码:

@Inject
EventBus bus;                                                   (1)

@GET
@Path("/hello")
public Uni<String> hello(@QueryParam("name") String name) {     (2)
    return bus.<String>request("greetings", name)               (3)
            .onItem().transform(response -> response.body());   (4)
}
1 注入事件总线。或者,您可以使用 vertx.eventBus()
2 我们通过查询参数接收一个名称
3 我们使用 request 方法启动请求-回复交互。我们将名称发送到 "greetings" 地址。
4 当收到响应时,我们提取响应体并将其作为 HTTP 响应返回。

现在,我们需要另一方:接收名称并进行回复的组件。创建 src/main/java/org/acme/GreetingService.java 文件,内容如下:

package org.acme;

import io.quarkus.vertx.ConsumeEvent;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped                          (1)
public class GreetingService {

    @ConsumeEvent("greetings")              (2)
    public String hello(String name) {      (3)
        return "Hello " + name;             (4)
    }
}
1 在应用程序作用域中声明一个 CDI Bean。Quarkus 将创建该类的单个实例。
2 使用 @ConsumeEvent 注释来声明一个消费者。也可以 直接使用 Vert.x API。
3 将消息负载作为方法参数接收。返回的对象将是回复。
4 返回响应。此响应将被发送回 VertxResource 类。

让我们尝试一下。在终端中,运行:

> curl "https://:8080/vertx/hello?name=bob"

您应该会收到预期的 Hello bob 消息。

使用 Vert.x 客户端

到目前为止,我们已经使用了 Vert.x 核心 API。Vert.x 提供了更多功能。它提供了一个庞大的生态系统。在本节中,我们将了解如何使用 Vert.x Web Client,一个响应式 HTTP 客户端。

请注意,一些 Quarkus 扩展封装了 Vert.x 客户端并为您管理它们。例如响应式数据源、Redis、邮件等。Web Client 不属于这种情况。

还记得吗,在本指南开头,我们将 smallrye-mutiny-vertx-web-client 依赖项添加到我们的 pom.xml 文件中。现在是时候使用它了。

首先,将以下导入添加到 src/main/java/org/acme/VertxResource.java 文件中:

import io.vertx.core.json.JsonArray;
import io.vertx.mutiny.ext.web.client.HttpResponse;
import io.vertx.mutiny.ext.web.client.WebClient;

接下来,我们需要创建一个 WebClient 实例。在构造函数中扩展 VertxResource 类,添加 client 字段以及 Web Client 的创建:

private final Vertx vertx;
private final WebClient client;            (1)

@Inject
public VertxResource(Vertx vertx) {
    this.vertx = vertx;
    this.client = WebClient.create(vertx); (2)
}
1 存储 WebClient,以便我们可以在 HTTP 端点中使用它。
2 创建 WebClient。请确保使用 io.vertx.mutiny.ext.web.client.WebClient 类。

现在让我们实现一个新的 HTTP 端点,该端点查询 Wikipedia API 以检索关于不同语言 Quarkus 页面的信息。将以下方法添加到 VertxResource 类中:

private static final String URL = "https://en.wikipedia.org/w/api.php?action=parse&page=Quarkus&format=json&prop=langlinks";

@GET
@Path("/web")
public Uni<JsonArray> retrieveDataFromWikipedia() {                     (1)
    return client.getAbs(URL).send()                                    (2)
            .onItem().transform(HttpResponse::bodyAsJsonObject)         (3)
            .onItem().transform(json -> json.getJsonObject("parse")     (4)
                                        .getJsonArray("langlinks"));
}
1 此端点返回一个 JSON 数组。Vert.x 提供了一种便捷的方式来操作 JSON 对象和数组。有关这些的更多详细信息,请参阅 参考指南
2 向 Wikipedia API 发送一个 GET 请求。
3 收到响应后,将其提取为 JSON 对象。
4 从响应中提取 langlinks 数组。

然后,通过以下方式调用端点:

> curl https://:8080/vertx/web
[{"lang":"de","url":"https://de.wikipedia.org/wiki/Quarkus","langname":"German","autonym":"Deutsch","*":"Quarkus"},{"lang":"fr","url":"https://fr.wikipedia.org/wiki/Quarkus","langname":"French","autonym":"français","*":"Quarkus"}]

响应表明,除了英文页面外,Wikipedia 上还有关于 Quarkus 的德语和法语页面。

从阻塞线程执行异步代码

有时有必要从阻塞线程执行异步代码。具体来说,是为了在具有隔离/复制的 Vert.x 上下文的 Vert.x 线程上执行代码。一个典型的例子是在应用程序启动期间需要利用 Hibernate Reactive API 的异步代码。Quarkus 提供了 VertxContextSupport#subscribeAndAwait() 方法,该方法在复制的 Vert.x 上下文上订阅提供的 io.smallrye.mutiny.Uni,然后阻塞当前线程并等待结果。

void onStart(@Observes StartupEvent event, Mutiny.SessionFactory sf) {
   VertxContextSupport.subscribeAndAwait(() -> {
      return sf.withTransaction(session -> session.persist(new Person()));
   });
}
如有必要,CDI 请求上下文将在异步代码执行期间激活。
VertxContextSupport#subscribeAndAwait() 不能在事件循环上调用!

也可以在复制的 Vert.x 上下文上订阅提供的 io.smallrye.mutiny.Multi。在这种情况下,当前线程不会被阻塞,并且将使用提供的订阅逻辑来消耗事件。

void onStart(@Observes StartupEvent event, ExternalService service) {
   VertxContextSupport.subscribeWith(() -> service.getFoos(), foo -> {
     // do something useful with foo
   });
}

更进一步

本指南介绍了如何在 Quarkus 应用中使用 Vert.x API。这只是一个简要概述。如果您想了解更多信息,请查看 关于 Quarkus 中 Vert.x 的参考指南

正如我们所见,事件总线是 Vert.x 应用的连接纽带。Quarkus 集成了它,以便不同的 Bean 可以与异步消息进行交互。这部分内容在 事件总线文档中进行了介绍。

使用 响应式 SQL 客户端,学习如何在 Quarkus 中实现高性能、低开销的数据库应用程序。