Using Eclipse Vert.x API from a Quarkus Application

Vert.x是一种用于构建响应式应用程序的工具包。如 Quarkus Reactive Architecture中所述,Quarkus 是着重底层 Vert.x。 image::quarkus-reactive-core.png[] Quarkus 应用程序能访问和使用 Vert.x API。 本指南介绍如何使用以下方法构建 Quarkus 应用程序:

  • 受管 vert.x 实例

  • the Vert.x event bus

  • the Vert.x Web Client

这是一份入门指南。Vert.x reference guide中介绍了垂直线和原生传输等更高级的功能。

Architecture

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

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

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

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

  4. `/vertx/web`使用 Vert.x Web 客户端从维基百科中检索数据

quarkus vertx guide architecture

Solution

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

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 vertx-quickstart directory中。

Mutiny

本指南使用了 Mutiny API。如果你不熟悉 Mutiny,请查看 Mutiny - an intuitive, reactive programming library

Bootstrapping the application

单击 this link配置你的应用程序。已选择一些扩展:

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

  • vertx,其提供对底层受管 Vert.x 的访问权限

单击 `Generate your application`按钮,下载 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 客户端,我们将使用它来实现 `/web`端点。

Accessing the managed Vert.x instance

创建 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 实例存储到字段中。

借助此,我们可以开始实现端点。

Using Vert.x Core 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 http://localhost:8080/vertx/lorem

你应当看到控制台中打印了该文件的内容。

Quarkus 提供了其他方式来处理静态文件。这是一个专为指南打造的示例。

Using Vert.x stream capability

对于小文件而言,读取文件并将其内容存储在内存中是行得通的,但大文件则不行。本节中,我们将会了解如何使用 Vert.x 流功能。

首先,下载 War and Peace 并将其存储在 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&lt;AsyncFile&gt;
3 当文件打开时,我们将检索一个 Multi,其中将包含块。
4 针对每个块,我们生成一个 String
5 为了在响应中可视化地查看块,我们追加了一个分隔符

然后,在终端中运行:

> curl http://localhost: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.”
...

Using the event bus

Vert.x 的核心功能之一是 event bus。它为应用程序提供基于消息的主干。因此,使用异步消息传递,组件之间可以交互,因此解除组件的耦合。你可以向单个消费者发送消息,也可以分派给多个消费者,或实现请求-响应交互,其中发送消息(请求)并期望获取响应。这正是我们将在此部分中使用的方式。我们的 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 我们接收一个 name 作为查询参数
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 directly
3 将消息有效负载作为方法参数接收。所返回的对象将成为回复。
4 返回响应。该响应将被发送回 VertxResource 类。

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

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

您应该得到预期的 Hello bob 消息回复。

Using Vert.x Clients

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

请注意,一些 Quarkus 扩展正在包装 Vert.x 客户端并为您管理它们。这种情况适用于响应式数据源、Redis、邮件……Web 客户端的情况并非如此。

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

首先,向 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 实例。使用 client 字段和在构造函数中创建 Web 客户端来扩展 VertxResource 类:

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 对象和数组。有关这些详情,请参见 the reference guide
2 向 Wikipedia API 发送 GET 请求
3 收到响应后,将其作为 JSON 对象提取
4 从响应中提取 langlinks 数组。

然后,使用以下内容调用此端点:

> curl http://localhost: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"}]

该响应表明,除了英语页面之外,还可以在维基百科上找到一个关于 Quarkus 的德语页面和一个法语页面。

Executing Asynchronous Code From a Blocking Thread

有时需要从阻塞线程执行异步代码。具体来说,使用已隔离/复制的 Vert.x 上下文在 Vert.x 线程上执行代码。一个典型的示例是,在应用程序启动期间需要利用 Hibernate 反射式 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
   });
}

Going further

本指南介绍了如何使用 Quarkus 应用程序中的 Vert.x API。这只是一个简要概览。如果您想了解更多信息,请查看 reference guide about Vert.x in Quarkus

正如我们所看到的,事件总线是 Vert.x 应用程序的连接组织。Quarkus 集成了它,以便不同的 Bean 可以与异步消息交互。本部分在 event bus documentation 中讨论。

了解如何在 Quarkus 上实现高性能、低开销的数据库应用程序,方法为 Reactive SQL Clients