Using Eclipse Vert.x API from a Quarkus Application

Vert.x是一种用于构建响应式应用程序的工具包。如 Quarkus Reactive Architecture中所述,Quarkus 是着重底层 Vert.x。

Vert.x is a toolkit for building reactive applications. As described in the Quarkus Reactive Architecture, Quarkus uses Vert.x underneath. image::quarkus-reactive-core.png[]

Quarkus 应用程序能访问和使用 Vert.x API。

Quarkus applications can access and use the Vert.x APIs.

本指南介绍如何使用以下方法构建 Quarkus 应用程序:

This guide presents how you can build a Quarkus application using:

  • the managed instance of Vert.x

  • the Vert.x event bus

  • the Vert.x Web Client

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

It’s an introductory guide. The Vert.x reference guide covers more advanced features such as verticles, and native transports.

Architecture

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

We are going to build a simple application exposing four HTTP endpoints:

  1. /vertx/lorem returns the content from a small file

  2. /vertx/book returns the content from a large file (a book)

  3. /vertx/hello uses the Vert.x event bus to produce the response

  4. /vertx/web uses the Vert.x Web Client to retrieve data from Wikipedia

quarkus vertx guide architecture

Solution

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

We recommend that you follow the instructions in the following sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

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

The solution is located in the vertx-quickstart directory.

Mutiny

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

This guide uses the Mutiny API. If you are not familiar with Mutiny, check Mutiny - an intuitive, reactive programming library.

Bootstrapping the application

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

Click on this link to configure your application. It selected a few extensions:

  • rest-jackson, which also brings rest. We are going to use it to expose our HTTP endpoints.

  • vertx, which provides access to the underlying managed Vert.x

单击 `Generate your application`按钮,下载 ZIP 文件并解压缩。然后,在你最喜欢的 IDE 中打开该项目。

Click on the Generate your application button, download the zip file and unzip it. Then, open the project in your favorite IDE.

如果你打开已生成的构建文件,则能看到已选择的扩展:

If you open the generated build file, you can see the selected extensions:

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")

在构建文件中时,添加以下依赖项:

While you are in your build file, add the following dependency:

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`端点。

This dependency provides the Vert.x Web Client, which we will be using to implement the /web endpoint.

Accessing the managed Vert.x instance

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

Create the src/main/java/org/acme/VertxResource.java file. It will contain our HTTP endpoints.

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

In this file, copy the following code:

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 Declare the root HTTP path.
2 We use constructor injection to receive the managed Vert.x instance. Field injection works too.
3 Receives the Vert.x instance as a constructor parameter
4 Store the managed Vert.x instance into a field.

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

With this, we can start implementing the endpoints.

Using Vert.x Core API

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

The injected Vert.x instance provides a set of APIs you can use. The one we are going to use in this section is the Vert.x File System. It provides a non-blocking API to access files.

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

In the src/main/resources directory, create a lorem.txt file with the following content:

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 文件中添加以下方法:

Then, in the VertxResource file add the following method:

@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 This endpoint handles HTTP GET request on path /lorem (so the full path will be vertx/lorem)
2 As the Vert.x API is asynchronous, our method returns a Uni. The content is written into the HTTP response when the asynchronous operation represented by the Uni completes.
3 We use the Vert.x file system API to read the created file
4 Once the file is read, the content is stored in an in-memory buffer. We transform this buffer into a String.

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

In a terminal, navigate to the root of the project and run:

Unresolved directive in vertx.adoc - include::{includes}/devtools/dev.adoc[]

在另一个终端中,运行:

In another terminal, run:

> curl http://localhost:8080/vertx/lorem

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

You should see the content of the file printed in the console.

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

Quarkus provides other ways to serve static files. This is an example made for the guide.

Using Vert.x stream capability

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

Reading a file and storing the content in memory works for small files, but not big ones. In this section, we will see how you can use Vert.x streaming capability.

首先,下载 War and Peace 并将其存储在 src/main/resources/book.txt 中。这是一个 3.2 Mb 的文件,虽然不算很大,但也说明了流的目的。这一次,我们不会将文件的内容累积在内存中然后分批写入,而是分块读取并逐个将这些块写入 HTTP 响应。

First, download War and Peace and store it in src/main/resources/book.txt. It’s a 3.2 Mb file, which, while not being huge, illustrates the purpose of streams. This time, we will not accumulate the file’s content in memory and write it in one batch, but read it chunk by chunk and write these chunks into the HTTP response one by one.

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

So, you should have the following files in your project:

.
├── 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 文件:

Add the following imports to the src/main/java/org/acme/VertxResource.java file:

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

将以下方法添加到 VertxResource 类:

Add the following method to the VertxResource class:

@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 This time, we return a Multi as we want to stream the chunks
2 We open the file using the open method. It returns a Uni<AsyncFile>
3 When the file is opened, we retrieve a Multi which will contain the chunks.
4 For each chunk, we produce a String
5 To visually see the chunks in the response, we append a separator

然后,在终端中运行:

Then, in a terminal, run:

> curl http://localhost:8080/vertx/book

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

It should retrieve the book content. In the output you should see the separator like:

...
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 响应返回。

One of the core features of Vert.x is the event bus. It provides a message-based backbone to your application. So, you can have components interacting using asynchronous message passing, and so decouple your components. You can send a message to a single consumer, or dispatch to multiple consumers, or implement a request-reply interaction, where you send a message (request) and expect a response. This is what we are going to use in this section. Our VertxResource will send a message containing a name to the greetings address. Another component will receive the message and produce the "hello $name" response. The VertxResource will receive the response and return it as the HTTP response.

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

So, first, add the following imports to the src/main/java/org/acme/VertxResource.java file:

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

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

Next, let’s extend our VertxResource class with the following code:

@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 Inject the event bus. Alternatively you can use vertx.eventBus().
2 We receive a name as a query parameter
3 We use the request method to initiate the request-reply interaction. We send the name to the "greetings" address.
4 When the response is received, we extract the body and return it as the HTTP response

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

Now, we need the other side: the component receiving the name and replying. Create the src/main/java/org/acme/GreetingService.java file with the following content:

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 Declaring a CDI Bean in the Application scope. Quarkus will create a single instance of this class.
2 Use the @ConsumeEvent annotation to declare a consumer. It is possible to use the Vert.x API directly too.
3 Receive the message payload as a method parameter. The returned object will be the reply.
4 Return the response. This response is sent back to the VertxResource class

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

Let’s try this. In a terminal, run:

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

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

You should get the expected Hello bob message back.

Using Vert.x Clients

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

So far, we have used the Vert.x Core API. Vert.x offers much more. It provides a vast ecosystem. In this section, we will see how you can use the Vert.x Web Client, a reactive HTTP client.

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

Note that some Quarkus extensions are wrapping Vert.x clients and manage them for you. That’s the case for the reactive data sources, Redis, mail…​ That’s not the case with the Web Client.

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

Remember, at the beginning of the guide, we added the smallrye-mutiny-vertx-web-client dependency to our pom.xml file. It’s now time to use it.

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

First, add the following imports to the src/main/java/org/acme/VertxResource.java file:

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 类:

Next, we need to create an instance of WebClient. Extend the VertxResource class with the client field and the creation of the web client in the constructor:

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

@Inject
public VertxResource(Vertx vertx) {
    this.vertx = vertx;
    this.client = WebClient.create(vertx); (2)
}
1 Store the WebClient, so we will be able to use it in our HTTP endpoint
2 Create the WebClient. Be sure to use the io.vertx.mutiny.ext.web.client.WebClient class

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

Let’s now implement a new HTTP endpoint that queries the Wikipedia API to retrieve the pages about Quarkus in the different languages. Add the following method to the VertxResource class:

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 This endpoint returns a JSON Array. Vert.x provides a convenient way to manipulate JSON Object and Array. More details about these in the reference guide.
2 Send a GET request to the Wikipedia API
3 Once the response is received, extract it as a JSON Object
4 Extract the langlinks array from the response.

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

Then, invoke the endpoint using:

> 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 的德语页面和一个法语页面。

The response indicates that, in addition to the English page, there are a German and a French page about Quarkus on wikipedia.

Executing Asynchronous Code From a Blocking Thread

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

Sometimes it’s necessary to execute an asynchronous code from a blocking thread. Specifically, to execute the code on a Vert.x thread with an isolated/duplicated Vert.x context. A typical example is an asynchronous code that needs to leverage the Hibernate Reactive API during application startup. Quarkus provides the VertxContextSupport#subscribeAndAwait() method which subscribes to the supplied io.smallrye.mutiny.Uni on a Vert.x duplicated context, then blocks the current thread and waits for the result.

void onStart(@Observes StartupEvent event, Mutiny.SessionFactory sf) {
   VertxContextSupport.subscribeAndAwait(() -> {
      return sf.withTransaction(session -> session.persist(new Person()));
   });
}

如有必要,异步代码执行期间会激活 CDI 请求上下文。

If necessary, the CDI request context is activated during execution of the asynchronous code.

请勿在事件循环中调用 VertxContextSupport#subscribeAndAwait()

VertxContextSupport#subscribeAndAwait() must not be called on an event loop!

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

It is also possible to subscribe to a supplied io.smallrye.mutiny.Multi on a Vert.x duplicated context. In this case, the current thread is not blocked and the supplied subscription logic is used to consume the events.

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

This guide introduced how you can use Vert.x APIs from a Quarkus application. It’s just a brief overview. If you want to know more, check the reference guide about Vert.x in Quarkus.

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

As we have seen, the event bus is the connecting tissue of Vert.x applications. Quarkus integrates it so different beans can interact with asynchronous messages. This part is covered in the event bus documentation.

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

Learn how to implement highly performant, low-overhead database applications on Quarkus with the Reactive SQL Clients.