宣布 RESTEasy Reactive

Quarkus 和 RESTEasy 团队非常高兴地宣布,RESTEasy Reactive 集成到 Quarkus 中已进入 Quarkus 的主仓库 [1],并将成为下一个 Quarkus 版本 1.11 的一部分。

我们期待大家对其进行测试并提供尽可能多的反馈。按照 Quarkus 的常规做法,该项目可以作为一组新的扩展来使用。

它是什么?

正如您从名称中可能猜到的那样,这项工作是**新的 JAX-RS 实现**,从头开始编写,以便在我们的通用 **Vert.x** 层上运行,因此是完全**响应式**的,同时还**与 Quarkus 紧密集成**,并因此将许多框架特定的工作(如注解扫描和元模型生成)移至**构建时**。

我为什么应该关心?

最简单的答案是,您可以继续利用广泛使用且非常强大的 JAX-RS API 为您的应用程序公开 REST 层,同时在应用程序可以达到的最大吞吐量方面获得显著改进。应用程序的启动速度也应该稍快一些,内存消耗也稍少一些。

我们的基准测试显示,使用这个新扩展的可衡量性能几乎与我们使用 Quarkus 的 Reactive Routes API(这本身就是一个非常有趣的 API,但通常更底层——更不用说开发者需要学习一个新 API 了)所能达到的性能相同。

此外,与提供基于注解的 REST 层的其他竞争性企业 Java 框架相比,Quarkus 的吞吐量高达其两倍,具体取决于基准测试。

还有哪些其他好处?

如果熟悉的 API 和新扩展大大改进的运行时特性还不够,我们还添加了一些非常令人兴奋且方便的新功能(这些功能不是 JAX-RS 规范的一部分),这些功能要么是社区请求的,要么是我们认为可以改善开发者体验并缓解规范的一些生硬之处。这些新功能包括:

默认非阻塞

所有端点现在默认在 IO 线程上运行。您可以使用 `@Blocking` 来更改此设置。

评分系统

在开发模式启动时,应用程序将向您显示端点列表,以及一个性能评分,告诉您为什么您的端点比最优版本慢。这对于找出如何提高 REST 性能非常有帮助。

新的请求/响应过滤器设计

JAX-RS 过滤器需要实现接口并将上下文对象注入为字段,这既昂贵又不灵活。基于我们在 Quarkus 构建系统中的成功,过滤器现在只是被注解的方法,任何参数都会被自动注入。

public class CustomContainerRequestFilter {

   @ServerRequestFilter
   public void whatever(UriInfo uriInfo, HttpHeaders httpHeaders, ContainerRequestContext requestContext) {
       String customHeaderValue = uriInfo.getPath() + "-" + httpHeaders.getHeaderString("some-input");
       requestContext.getHeaders().putSingle("custom-header", customHeaderValue);
   }
}

此外,如果过滤器需要执行阻塞操作,那么它们可以返回 `Uni`,RESTEasy Reactive 在执行过滤器时不会阻塞事件循环线程。

最后,虽然我们还没有这样做,但这种方法可以轻松地扩展到其他类型的 JAX-RS 提供者,从而完全无需在其代码中使用 `@Context`。

新的 `*Param` 注解

这些注解用于替代 JAX-RS 的 `@PathParam`, `@QueryParam` 等注解,而无需指定名称。我们选择不重用相同的注解名称是为了避免与 JAX-RS 或其他 EE 规范冲突。

@POST
@Path("params/{p}")
public String params(@RestPath String p,
                    @RestQuery String q,
                    @RestHeader int h,
                    @RestForm String f,
                    @RestMatrix String m,
                    @RestCookie String c) {
   return "params: p: " + p + ", q: " + q + ", h: " + h + ", f: " + f + ", m: " + m + ", c: " + c;
}
更简单的参数和上下文注入

使用 RESTEasy Reactive,如果您的参数名称与路径参数相同,您甚至不需要使用 `@PathParam` 或 `@RestPath`,同样,您可以跳过所有已知上下文类型的 `@Context`,这使其更加简单。

@POST
@Path("params/{p}")
public String params(String p, UriInfo info) {
   return "params: p: " + p + ", info: " + info;
}
新的最佳消息体读取器/写入器

如果在服务某个端点时没有调用过滤器和拦截器,您可以使用更高效的消息体写入器,它们直接写入 vert.x,并且不需要反射和注解。

@Provider
public class ServerVertxBufferMessageBodyWriter extends VertxBufferMessageBodyWriter
       implements ServerMessageBodyWriter<Buffer> {

   @Override
   public boolean isWriteable(Class<?> type, ResteasyReactiveResourceInfo target, MediaType mediaType) {
       return true;
   }

   @Override
   public void writeResponse(Buffer buffer, ServerRequestContext context) {
       context.serverResponse().end(buffer.getBytes());
   }
}
默认内容类型

返回 String 的端点默认生成 text/plain。我们计划对 JSON 和其他类型也这样做。

CDI 集成

所有通过 JAX-RS 的 @Context 的注入都委托给 Arc。这使用户能够获得 Arc 为 Quarkus 所有其他部分带来的构建时注入的优势。

每个类的异常映射器

在 JAX-RS 规范中,没有办法为特定的 JAX-RS 资源类以不同的方式处理异常——所有异常映射都是全局进行的。

然而,在 RESTEasy Reactive 中,您只需执行类似的操作

@Path("first")
public class FirstResource {

   @GET
   @Produces("text/plain")
   public String throwsVariousExceptions(@RestQuery String name) {
       if (name.startsWith("IllegalArgument")) {
           throw new IllegalArgumentException();
       } else if (name.startsWith("IllegalState")) {
           throw new IllegalStateException("IllegalState");
       } else if (name.startsWith("My")) {
           throw new MyException();
       }
       throw new RuntimeException();
   }

   @ServerExceptionMapper({ IllegalStateException.class, IllegalArgumentException.class })
   public Response handleIllegal() {
       return Response.status(409).build();
   }

   @ServerExceptionMapper(MyException.class)
   public Response handleMy(SimpleResourceInfo simplifiedResourceInfo, MyException myException,
           ContainerRequestContext containerRequestContext, UriInfo uriInfo, HttpHeaders httpHeaders, Request request) {
       return Response.status(410).entity(uriInfo.getPath() + "->" + simplifiedResourceInfo.getMethodName()).build();
   }
}

即可自定义某些资源类的异常处理。

另请注意,`@ServerExceptionMapper` 可以用于以全局方式处理异常,就像 JAX-RS 使用 `ExceptionMapper` 一样。为此,只需将一个不属于资源类的所有者的方法注解为 `@ServerExceptionMapper` 即可。

其他扩展是否与其一起工作?

当然!

与现有 quarkus-resteasy 扩展集成的扩展也与 quarkus-resteasy-reactive 扩展集成。因此,您可以继续使用 *CDI*、*Security*、*Metrics*、*JSON*、*Qute*、*Bean Validation*、*OpenAPI*,并享受出色的开箱即用和完整的开发体验。

我如何尝试?

该项目已进入 Quarkus 主分支,因此如果您渴望尝试,则必须通过链接构建 Quarkus 的源代码,并按照链接使用正确的 BOM 和版本。

此外,您还可以使用 Maven Snapshots(因为 Quarkus 快照构建每天都会上传到 Sonatype),通过将版本 `999-SNAPSHOT` 指定为 Quarkus 版本,并使用 `quarkus-bom` 而不是 `quarkus-universe-bom` 作为 BOM。有多种方法可以在 Maven 中启用快照版本。 StackOverflow 回答显示了可以在每个项目或全局使用的配置。

可用的 RESTEasy Reactive 扩展是:

  • quarkus-resteasy-reactive

  • quarkus-resteasy-reactive-jackson

  • quarkus-resteasy-reactive-jsonb

  • quarkus-resteasy-reactive-qute

这些扩展相当于现有的 *quarkus-resteasy* 扩展,因此,只需在您的应用程序中从 *quarkus-resteasy-jackson* 切换到 *quarkus-resteasy-reactive-jackson*,就可以让您尝试 RESTEasy Reactive 与 Jackson 集成。

此外,如果您需要使用 JAX-RS 客户端(这不是声明式的 MicroProfile REST Client,而是 JAX-RS 规范指定的编程客户端),可以使用 `quarkus-jaxrs-client` 扩展。

我应该注意什么?

  • 首先要注意的是,目前这套扩展被认为是实验性的。尽管该项目通过了几乎所有的 JAX-RS TCK,但这只是第一个版本,所以请记住,它可能比典型的久经考验的库有更多的错误,而一些新的 API 和 SPI 可能会被破坏。尽管这是第一个版本,但我们确实希望这项工作在不久的将来成为 Quarkus 的默认 REST 层。

  • 如新功能部分所述,请求默认由事件循环线程提供服务。这确保了最大的吞吐量,但也意味着不应该在这些线程上执行任何阻塞操作。如果您使用阻塞 IO(例如,通过 Hibernate Panache 访问数据库),请务必在方法或类上使用 `@Blocking` 注解。这将确保请求由工作线程提供服务。不言而喻,我们也对您关于此默认设置的反馈非常感兴趣。

  • 还没有文档。文档将在正式发布 1.11 版本之前添加,并将逐步改进。此电子邮件应包含您入门所需的所有信息,但如果您遇到任何麻烦,我们可以在任何通常的渠道(Zulip 聊天、邮件列表、GitHub Issues、StackOverflow)提供帮助。

缺少哪些 JAX-RS 功能?

我们决定专注于现代 REST 层所需的大多数用户功能,而不是实现 JAX-RS TCK 所需的每项功能。因此,RESTEasy Reactive 不支持 XML,并且该规范的各种晦涩的功能也不支持(例如 *javax.activation.DataSource*、*javax.annotation.ManagedBean*、*javax.ws.rs.core.StreamingOutput*)。

此外,值得注意的是,第一个版本将不包含基于新的 JAX-RS 客户端(有一个专用的扩展)的 MicroProfile REST Client 的实现。这在不久的将来很可能会改变。

下一步是什么?

尽管新扩展将随常规的 *1.11* 版本一起提供,但我们正在考虑发布一个 `1.11.0.Alpha1` 版本,以便您尽可能轻松地尝试新扩展并提供早期反馈。

我们非常期待听到您对 RESTEasy Reactive 在 Quarkus 中的使用及其体验的看法,并计划将其好好利用以进一步改进项目。


1. 目前主要的 RESTEasy Reactive 组件位于 Quarkus 主仓库的 https://github.com/quarkusio/quarkus/tree/main/independent-projects/resteasy-reactive;但是,计划是在事情稳定后,将此代码移至 https://github.com/resteasy/resteasy-reactive。此移动不应以任何方式影响 quarkus-resteasy-reactive 扩展的用户,如果您将来阅读此博客帖子并且找不到它,只是提醒一下。