编辑此页面

RESTEasy Classic

本指南介绍的是 RESTEasy Classic,它是 Quarkus 2.8 之前的默认 Jakarta REST(前身为 JAX-RS)实现。

现在推荐使用 Quarkus REST(前身为 RESTEasy Reactive),它能同样出色地支持传统的阻塞式和响应式工作负载。

有关 Quarkus REST 的更多信息,请参阅 RESTful JSON 入门指南Quarkus REST 参考文档

如果您需要一个基于 RESTEasy Classic 的 REST 客户端(包括对 JSON 的支持),还有另一份指南。

架构

本指南中的应用程序很简单:用户可以通过表单向列表中添加元素,列表也会相应地更新。

浏览器和服务器之间的所有信息都以 JSON 格式格式化。

创建 Maven 项目

首先,我们需要一个新项目。使用以下命令创建一个新项目

CLI
quarkus create app org.acme:rest-json-quickstart \
    --extension='resteasy-jackson' \
    --no-code
cd rest-json-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions='resteasy-jackson' \
    -DnoCode
cd rest-json-quickstart

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=rest-json-quickstart"

此命令将生成一个导入 RESTEasy/Jakarta REST 和 Jackson 扩展的新项目,特别是添加了以下依赖

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

为了改善用户体验,Quarkus 注册了三个 Jackson Java 8 模块,因此您无需手动操作。

Quarkus 还支持 JSON-B,因此如果您更喜欢 JSON-B 而不是 Jackson,则可以创建依赖于 RESTEasy JSON-B 扩展的项目。

CLI
quarkus create app org.acme:rest-json-quickstart \
    --extension='resteasy-jsonb' \
    --no-code
cd rest-json-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions='resteasy-jsonb' \
    -DnoCode
cd rest-json-quickstart

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=rest-json-quickstart"

此命令将生成一个导入 RESTEasy/Jakarta REST 和 JSON-B 扩展的新项目,特别是添加了以下依赖

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-jsonb")

创建第一个 JSON REST 服务

在此示例中,我们将创建一个应用程序来管理水果列表。

首先,我们创建 Fruit bean 如下:

package org.acme.rest.json;

public class Fruit {

    public String name;
    public String description;

    public Fruit() {
    }

    public Fruit(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

没什么特别的。需要注意的重要一点是,JSON 序列化层需要一个默认构造函数。

现在,创建 org.acme.rest.json.FruitResource 类如下:

package org.acme.rest.json;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;

import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

@Path("/fruits")
public class FruitResource {

    private Set<Fruit> fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));

    public FruitResource() {
        fruits.add(new Fruit("Apple", "Winter fruit"));
        fruits.add(new Fruit("Pineapple", "Tropical fruit"));
    }

    @GET
    public Set<Fruit> list() {
        return fruits;
    }

    @POST
    public Set<Fruit> add(Fruit fruit) {
        fruits.add(fruit);
        return fruits;
    }

    @DELETE
    public Set<Fruit> delete(Fruit fruit) {
        fruits.removeIf(existingFruit -> existingFruit.name.contentEquals(fruit.name));
        return fruits;
    }
}

实现非常简单,您只需使用 Jakarta REST 注释定义端点即可。

Fruit 对象将根据您在初始化项目时选择的扩展,自动由 JSON-BJackson 进行序列化/反序列化。

当安装了 quarkus-resteasy-jacksonquarkus-resteasy-jsonb 等 JSON 扩展时,Quarkus 会默认将大多数返回值设置为 application/json 媒体类型。您可以使用 @Produces@Consumes 注释覆盖此设置,但某些已知类型(如 String(默认为 text/plain)和 File(默认为 application/octet-stream))除外。

要禁用默认的 JSON 行为,请将 quarkus.resteasy-json.default-json=false 设置为 application.properties,默认行为将恢复为自动协商。在这种情况下,您必须在端点中包含 @Produces(MediaType.APPLICATION_JSON)@Consumes(MediaType.APPLICATION_JSON) 注释才能使用 JSON。

如果您不依赖于 JSON 默认设置,强烈建议在端点上使用 @Produces@Consumes 注释来精确指定预期的内容类型。这有助于减少包含在原生可执行文件中的 Jakarta REST 提供程序(本质上是转换器)的数量。

配置 JSON 支持

Jackson

在 Quarkus 中,通过 CDI 获取的默认 Jackson ObjectMapper(由 Quarkus 扩展使用)设置为忽略未知属性(通过禁用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)。

要恢复 Jackson 的默认行为,请在 application.properties 中将 quarkus.jackson.fail-on-unknown-properties=true 设置为 true,或使用 @JsonIgnoreProperties(ignoreUnknown = false) 为每个类单独设置。

此外,ObjectMapper 会以 ISO-8601 格式化日期和时间(通过禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)。

要恢复 Jackson 的默认行为,请在 application.properties 中使用 quarkus.jackson.write-dates-as-timestamps=true。对于单个字段的自定义日期格式,请使用 @JsonFormat 注释。

Quarkus 通过 CDI bean 简化了 Jackson 的配置。创建一个类型为 io.quarkus.jackson.ObjectMapperCustomizer 的 CDI bean 来应用各种 Jackson 设置。以下是注册自定义模块的示例:

@ApplicationScoped
public class MyObjectMapperCustomizer implements ObjectMapperCustomizer {
    @Override
    public void customize(ObjectMapper objectMapper) {
        // Add custom Jackson configuration here
    }
}

建议使用此方法来配置 Jackson 设置。

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.jackson.ObjectMapperCustomizer;
import jakarta.inject.Singleton;

@Singleton
public class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer {

    public void customize(ObjectMapper mapper) {
        mapper.registerModule(new CustomModule());
    }
}

用户甚至可以选择提供自己的 ObjectMapper bean。如果这样做,请务必在生成 ObjectMapper 的 CDI producer 中手动注入并应用所有 io.quarkus.jackson.ObjectMapperCustomizer bean。否则,将无法应用各种扩展提供的 Jackson 特定自定义。

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.arc.All;
import io.quarkus.jackson.ObjectMapperCustomizer;
import java.util.List;
import jakarta.inject.Singleton;

public class CustomObjectMapper {

    // Replaces the CDI producer for ObjectMapper built into Quarkus
    @Singleton
    ObjectMapper objectMapper(@All List<ObjectMapperCustomizer> customizers) {
        ObjectMapper mapper = myObjectMapper(); // Custom `ObjectMapper`

        // Apply all ObjectMapperCustomizer beans (incl. Quarkus)
        for (ObjectMapperCustomizer customizer : customizers) {
            customizer.customize(mapper);
        }

        return mapper;
    }
}

JSON-B

如上所述,Quarkus 通过使用 quarkus-resteasy-jsonb 扩展提供了使用 JSON-B 而不是 Jackson 的选项。

遵循与上一节中描述的相同方法,可以使用 io.quarkus.jsonb.JsonbConfigCustomizer bean 来配置 JSON-B。

例如,如果需要为类型 com.example.Foo 注册一个名为 FooSerializer 的自定义序列化器,添加如下 bean 即可:

import io.quarkus.jsonb.JsonbConfigCustomizer;
import jakarta.inject.Singleton;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.serializer.JsonbSerializer;

@Singleton
public class FooSerializerRegistrationCustomizer implements JsonbConfigCustomizer {

    public void customize(JsonbConfig config) {
        config.withSerializers(new FooSerializer());
    }
}

更高级的选项是直接提供一个 jakarta.json.bind.JsonbConfig bean(作用域为 Dependent),或者在极端情况下,提供一个 jakarta.json.bind.Jsonb 类型的 bean(作用域为 Singleton)。如果采用后一种方法,请务必在生成 jakarta.json.bind.Jsonb 的 CDI producer 中手动注入并应用所有 io.quarkus.jsonb.JsonbConfigCustomizer bean。否则,将无法应用各种扩展提供的 JSON-B 特定自定义。

import io.quarkus.jsonb.JsonbConfigCustomizer;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Instance;
import jakarta.json.bind.JsonbConfig;

public class CustomJsonbConfig {

    // Replaces the CDI producer for JsonbConfig built into Quarkus
    @Dependent
    JsonbConfig jsonConfig(Instance<JsonbConfigCustomizer> customizers) {
        JsonbConfig config = myJsonbConfig(); // Custom `JsonbConfig`

        // Apply all JsonbConfigCustomizer beans (incl. Quarkus)
        for (JsonbConfigCustomizer customizer : customizers) {
            customizer.customize(config);
        }

        return config;
    }
}

HAL 标准是一种表示 Web 链接的简单格式。

要启用 HAL 支持,请将 quarkus-hal 扩展添加到您的项目中。此外,由于 HAL 需要 JSON 支持,您需要添加 quarkus-resteasy-jsonbquarkus-resteasy-jackson 扩展。

表 1. 表上下文对象
GAV 用法

io.quarkus:quarkus-hal

HAL

添加扩展后,我们现在可以注解 REST 资源以生成媒体类型 application/hal+json(或使用 RestMediaType.APPLICATION_HAL_JSON)。例如:

@Path("/records")
public class RecordsResource {

    @GET
    @Produces({ MediaType.APPLICATION_JSON, "application/hal+json" })
    @LinkResource(entityClassName = "org.acme.Record", rel = "list")
    public List<TestRecord> getAll() {
        // ...
    }

    @GET
    @Path("/first")
    @Produces({ MediaType.APPLICATION_JSON, "application/hal+json" })
    @LinkResource(rel = "first")
    public TestRecord getFirst() {
        // ...
    }
}

现在,端点 /records/records/first 将接受 jsonhal+json 媒体类型,以 HAL 格式打印记录。

例如,如果我们使用 curl 调用 /records 端点以返回记录列表,HAL 格式将如下所示:

& curl -H "Accept:application/hal+json" -i localhost:8080/records
{
    "_embedded": {
        "items": [
            {
                "id": 1,
                "slug": "first",
                "value": "First value",
                "_links": {
                    "list": {
                        "href": "https://:8081/records"
                    },
                    "first": {
                        "href": "https://:8081/records/first"
                    }
                }
            },
            {
                "id": 2,
                "slug": "second",
                "value": "Second value",
                "_links": {
                    "list": {
                        "href": "https://:8081/records"
                    },
                    "first": {
                        "href": "https://:8081/records/first"
                    }
                }
            }
        ]
    },
    "_links": {
        "list": {
            "href": "https://:8081/records"
        }
    }
}

当我们调用返回单个实例的资源 /records/first 时,输出是:

& curl -H "Accept:application/hal+json" -i localhost:8080/records/first
{
    "id": 1,
    "slug": "first",
    "value": "First value",
    "_links": {
        "list": {
            "href": "https://:8081/records"
        },
        "first": {
            "href": "https://:8081/records/first"
        }
    }
}

创建一个前端

现在,让我们添加一个简单的网页来与我们的 FruitResource 进行交互。Quarkus 会自动服务位于 META-INF/resources 目录下的静态资源。在 src/main/resources/META-INF/resources 目录中,添加一个 fruits.html 文件,其中包含此 fruits.html 文件中的内容。

您现在可以与您的 REST 服务交互

  • 使用以下命令启动 Quarkus

    CLI
    quarkus dev
    Maven
    ./mvnw quarkus:dev
    Gradle
    ./gradlew --console=plain quarkusDev
  • 打开浏览器访问 https://:8080/fruits.html

  • 通过表单将新水果添加到列表中

构建本机可执行文件

您可以使用常用命令构建本机可执行文件

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

运行它就像执行 ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner 一样简单。

然后,您可以将您的浏览器指向 https://:8080/fruits.html 并使用您的应用程序。

关于序列化

JSON 序列化库使用 Java 反射来获取对象的属性并进行序列化。

当使用 GraalVM 的原生可执行文件时,所有将用于反射的类都需要被注册。好消息是 Quarkus 大部分时间都会为您处理这项工作。到目前为止,我们还没有为反射使用注册任何类,甚至没有注册 Fruit,一切都运行正常。

当 Quarkus 能够从 REST 方法推断出序列化的类型时,它会执行一些魔法。当您拥有以下 REST 方法时,Quarkus 会确定 Fruit 将被序列化:

@GET
public List<Fruit> list() {
    // ...
}

Quarkus 会在构建时通过分析 REST 方法自动为您完成这些操作,这就是为什么在本指南的第一部分我们不需要任何反射注册。

Jakarta REST 世界中的另一个常见模式是使用 Response 对象。Response 附带一些很好的优点:

  • 您可以根据方法中的情况返回不同的实体类型(例如 LegumeError)。

  • 您可以设置 Response 的属性(错误情况下的状态码)。

您的 REST 方法将如下所示:

@GET
public Response list() {
    // ...
}

Quarkus 在构建时无法确定 Response 中包含的类型,因为信息不可用。在这种情况下,Quarkus 将无法自动注册所需类的反射。

这就引出了我们的下一部分。

使用 Response

让我们创建 Legume 类,它将作为 JSON 序列化,遵循与我们的 Fruit 类相同的模型:

package org.acme.rest.json;

public class Legume {

    public String name;
    public String description;

    public Legume() {
    }

    public Legume(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

现在让我们创建一个 LegumeResource REST 服务,其中只有一个方法可以返回豆类列表。

此方法返回一个 Response 而不是 Legume 列表。

package org.acme.rest.json;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/legumes")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class LegumeResource {

    private Set<Legume> legumes = Collections.synchronizedSet(new LinkedHashSet<>());

    public LegumeResource() {
        legumes.add(new Legume("Carrot", "Root vegetable, usually orange"));
        legumes.add(new Legume("Zucchini", "Summer squash"));
    }

    @GET
    public Response list() {
        return Response.ok(legumes).build();
    }
}

现在,让我们添加一个简单的网页来显示我们的豆类列表。在 src/main/resources/META-INF/resources 目录中,添加一个 legumes.html 文件,其中包含此 legumes.html 文件中的内容。

在浏览器中打开 https://:8080/legumes.html,您将看到我们的豆类列表。

当将应用程序作为原生可执行文件运行时,有趣的部分就开始了。

  • 使用以下命令创建原生可执行文件:

    CLI
    quarkus build --native
    Maven
    ./mvnw install -Dnative
    Gradle
    ./gradlew build -Dquarkus.native.enabled=true
  • 使用 ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner 执行它。

  • 打开浏览器并转到 https://:8080/legumes.html

那里没有豆类。

如上所述,问题在于 Quarkus 无法确定 Legume 类,该类需要通过分析 REST 端点来进行一些反射。JSON 序列化库尝试获取 Legume 的字段列表,但得到一个空列表,因此它不会序列化字段数据。

目前,当 JSON-B 或 Jackson 尝试获取类的字段列表时,如果类未注册用于反射,则不会抛出异常。GraalVM 将返回一个空字段列表。

希望将来会有所改变,使错误更加明显。

我们可以通过在 Legume 类上添加 @RegisterForReflection 注释来手动注册 Legume 进行反射。

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection
public class Legume {
    // ...
}
@RegisterForReflection 注释指示 Quarkus 在原生编译期间保留该类及其成员。有关 @RegisterForReflection 注释的更多详细信息,请参阅 原生应用程序提示页面。

让我们这样做,并遵循与之前相同的步骤:

  • Ctrl+C 停止应用程序。

  • 使用以下命令创建原生可执行文件:

    CLI
    quarkus build --native
    Maven
    ./mvnw install -Dnative
    Gradle
    ./gradlew build -Dquarkus.native.enabled=true
  • 使用 ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner 执行它。

  • 打开浏览器并转到 https://:8080/legumes.html

这次,您可以看到我们的豆类列表。

响应式编程

对于响应式工作负载,请始终使用 Quarkus REST

您可以返回*响应式类型*来处理异步处理。Quarkus 推荐使用 Mutiny 来编写响应式和异步代码。

要集成 Mutiny 和 RESTEasy,您需要将 quarkus-resteasy-mutiny 依赖添加到您的项目中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-mutiny</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-mutiny")

然后,您的端点可以返回 UniMulti 实例:

@GET
@Path("/{name}")
public Uni<Fruit> getOne(@PathParam String name) {
    return findByName(name);
}

@GET
public Multi<Fruit> getAll() {
    return findAll();
}

当您有一个单个结果时,使用 Uni。当您有多个可能异步发出的项时,使用 Multi

您可以使用 UniResponse 返回异步 HTTP 响应:Uni<Response>

有关 Mutiny 的更多详细信息,请参阅 Mutiny - 直观的响应式编程库

HTTP 过滤器和拦截器

可以通过提供相应的 ContainerRequestFilterContainerResponseFilter 实现来拦截 HTTP 请求和响应。这些过滤器适用于处理与消息相关的元数据:HTTP 头、查询参数、媒体类型和其他元数据。它们还可以中止请求处理,例如,当用户没有权限访问端点时。

让我们使用 ContainerRequestFilter 为我们的服务添加日志记录功能。我们可以通过实现 ContainerRequestFilter 并用 @Provider 注释来完成此操作:

package org.acme.rest.json;

import io.vertx.core.http.HttpServerRequest;
import org.jboss.logging.Logger;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Provider;

@Provider
public class LoggingFilter implements ContainerRequestFilter {

    private static final Logger LOG = Logger.getLogger(LoggingFilter.class);

    @Context
    UriInfo info;

    @Context
    HttpServerRequest request;

    @Override
    public void filter(ContainerRequestContext context) {

        final String method = context.getMethod();
        final String path = info.getPath();
        final String address = request.remoteAddress().toString();

        LOG.infof("Request %s %s from IP %s", method, path, address);
    }
}

现在,每当调用 REST 方法时,请求都会被记录到控制台。

2019-06-05 12:44:26,526 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /legumes from IP 127.0.0.1
2019-06-05 12:49:19,623 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /fruits from IP 0:0:0:0:0:0:0:1
2019-06-05 12:50:44,019 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request POST /fruits from IP 0:0:0:0:0:0:0:1
2019-06-05 12:51:04,485 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /fruits from IP 127.0.0.1

CORS 过滤器

跨域资源共享 (CORS) 是一种机制,它允许从与第一个资源服务的域不同的域请求网页上的受限资源。

Quarkus 在 HTTP 层包含一个 CORS 过滤器。有关 CORS 过滤器的更多信息及其用法,请参阅 Quarkus“跨域资源共享”指南的 CORS 过滤器 部分。

GZip 支持

Quarkus 带有 GZip 支持(尽管默认情况下未启用)。以下配置选项允许配置 GZip 支持。

quarkus.resteasy.gzip.enabled=true (1)
quarkus.resteasy.gzip.max-input=10M (2)
1 启用 Gzip 支持。
2 配置压缩请求体的上限。这有助于通过限制其范围来减轻潜在攻击。默认值为 10M。此配置选项可以识别以下格式的字符串(显示为正则表达式):[0-9]+[KkMmGgTtPpEeZzYy]?。如果未给出后缀,则假定为字节。

一旦启用了 GZip 支持,您可以通过将 @org.jboss.resteasy.annotations.GZIP 注释添加到您的端点方法来在端点上使用它。

还有一个 quarkus.http.enable-compression 配置属性,它全局启用 HTTP 响应压缩。如果启用,当 Content-Type HTTP 头被设置,并且其值是根据 quarkus.http.compress-media-types 配置属性配置的压缩媒体类型时,响应体将被压缩。

Multipart 支持

RESTEasy 通过 RESTEasy Multipart Provider 支持 multipart。

Quarkus 提供了一个名为 quarkus-resteasy-multipart 的扩展,让您的工作更轻松。

此扩展与 RESTEasy 的默认行为略有不同,因为默认字符集(如果请求中未指定)是 UTF-8,而不是 US-ASCII。

您可以使用以下配置属性配置此行为:

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

默认字符集。

请注意,默认值是 UTF-8,这与 RESTEasy 的默认值 US-ASCII 不同。

环境变量:QUARKUS_RESTEASY_MULTIPART_INPUT_PART_DEFAULT_CHARSET

显示更多

字符集

UTF-8

默认内容类型。

环境变量:QUARKUS_RESTEASY_MULTIPART_INPUT_PART_DEFAULT_CONTENT_TYPE

显示更多

字符串

text/plain

Servlet 兼容性

在 Quarkus 中,RESTEasy 可以直接运行在 Vert.x HTTP 服务器之上,或者如果您有任何 Servlet 依赖,则可以运行在 Undertow 之上。

因此,某些类,例如 HttpServletRequest,并不总是可用于注入。此特定类的多数用例都有 Jakarta REST 等效项,但获取远程客户端 IP 除外。

RESTEasy 提供了一个您可以注入的替代 API:HttpRequest,它具有 getRemoteAddress()getRemoteHost() 方法来解决此问题。

RESTEasy 和 REST Client 交互

在 Quarkus 中,RESTEasy 扩展和 REST Client 扩展共享相同的底层基础。一个重要的结果是它们共享相同的提供程序列表(在 Jakarta REST 的意义上)。

例如,如果您声明了一个 WriterInterceptor,它默认会拦截服务器调用和客户端调用,这可能不是期望的行为。

但是,您可以通过添加 @ConstrainedTo(RuntimeType.CLIENT) 注释到提供程序来更改此默认行为并限制提供程序:

  • 仅考虑*服务器*调用,方法是在您的提供程序上添加 @ConstrainedTo(RuntimeType.SERVER) 注释;

  • 仅考虑*客户端*调用,方法是在您的提供程序上添加 @ConstrainedTo(RuntimeType.CLIENT) 注释。

与 Jakarta EE 开发的区别

无需 Application

支持通过应用程序提供的 Application 子类进行配置,但不是必需的。

仅一个 Jakarta REST 应用程序

与在标准 Servlet 容器中运行的 Jakarta REST(和 RESTEasy)不同,Quarkus 只支持部署一个 Jakarta REST 应用程序。如果定义了多个 Jakarta REST Application 类,构建将失败并显示消息 Multiple classes have been annotated with @ApplicationPath which is currently not supported

如果定义了多个 Jakarta REST 应用程序,可以使用属性 quarkus.resteasy.ignore-application-classes=true 来忽略所有显式的 Application 类。这使得所有资源类都可以通过 quarkus.resteasy.path(默认为 /)定义的应用程序路径进行访问。

Jakarta REST 应用程序的支持限制

RESTEasy 扩展不支持 jakarta.ws.rs.core.Application 类的 getProperties() 方法。此外,它仅依赖于 getClasses()getSingletons() 方法来过滤带注释的资源、提供程序和功能类。它不会过滤内置的资源、提供程序和功能类,也不会过滤其他扩展注册的资源、提供程序和功能类。最后,getSingletons() 方法返回的对象被忽略,只考虑类来过滤资源、提供程序和功能类,换句话说,getSingletons() 方法与 getClasses() 的处理方式相同。

资源生命周期

在 Quarkus 中,所有 Jakarta REST 资源都被视为 CDI Bean。可以通过 @Inject 注入其他 Bean,使用 @Transactional 等绑定注入拦截器,定义 @PostConstruct 回调等。

如果在资源类上未声明作用域注释,则作用域将默认为。quarkus.resteasy.singleton-resources 属性可以控制默认作用域。

如果设置为 true(默认值),则会创建一个资源类的*单个实例*来服务所有请求(如 @jakarta.inject.Singleton 所定义的)。

如果设置为 false,则为每个请求创建一个资源类的*新实例*。

显式的 CDI 作用域注释(@RequestScoped@ApplicationScoped 等)始终会覆盖默认行为,并指定资源实例的生命周期。

@Context 元素不是通过 CDI 注入的,因此不能通过构造函数注入。请在资源的字段中注入 @Context 元素。

使用构建时条件包含/排除 Jakarta REST 类

Quarkus 允许直接通过构建时条件包含或排除 Jakarta REST 资源、提供程序和功能,就像它为 CDI Bean 所做的那样。因此,各种 Jakarta REST 类可以被注解上配置文件条件(@io.quarkus.arc.profile.IfBuildProfile@io.quarkus.arc.profile.UnlessBuildProfile)和/或属性条件(io.quarkus.arc.properties.IfBuildPropertyio.quarkus.arc.properties.UnlessBuildProperty),以便在构建时指示 Quarkus 在什么条件下应该包含这些 Jakarta REST 类。

在下面的示例中,Quarkus 将包含 sayHello 端点,当且仅当启用了构建配置文件 app1

@IfBuildProfile("app1")
public class ResourceForApp1Only {

    @GET
    @Path("sayHello")
    public String sayHello() {
        return "hello";
     }
}

请注意,如果检测到 Jakarta REST 应用程序并且重写了 getClasses() 和/或 getSingletons() 方法,Quarkus 将忽略构建时条件,并且只考虑 Jakarta REST 应用程序中定义的内容。

结论

使用 Quarkus 创建 JSON REST 服务很容易,因为它依赖于经过验证且知名的技术。

一如既往,Quarkus 在后台进一步简化了运行应用程序作为原生可执行文件的过程。

只有一件事需要记住:如果您使用 Response 并且 Quarkus 无法确定将被序列化的 Bean,您需要用 @RegisterForReflection 注解它们。

相关内容