RESTEasy Classic

Architecture

本指南中创建的应用程序非常简单:用户可以通过表单向列表中添加元素,并将相应地更新列表。

浏览器和服务器之间所有信息都采用 JSON 格式。

Creating the Maven project

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

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

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

适用于 Windows 用户:

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

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令生成一个导入 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 modules,因此您无需手动进行此操作。

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

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

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

适用于 Windows 用户:

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

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令生成一个导入 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")

Creating the first JSON REST service

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

首先,让我们按如下方式创建 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 注解定义您的端点。

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

当安装了如 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,默认值将返回为自动协商。在这种情况下,您必须在端点中包含 @Produces(MediaType.APPLICATION_JSON)@Consumes(MediaType.APPLICATION_JSON) 注解才能使用 JSON。 如果您不依赖于 JSON 默认值,强烈建议在端点上使用 @Produces@Consumes 注解来明确指定预期内容类型。这有助于减少本机可执行文件中包含的 Jakarta REST 提供程序(本质上是转换器)的数量。

Configuring JSON support

Jackson

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

若要恢复 Jackson 的默认行为,请在 application.properties 中设置 quarkus.jackson.fail-on-unknown-properties=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 产生器中应用所有 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。

例如,如果需要使用 JSON-B 注册名为 FooSerializer 的类型 com.example.Foo 的自定义序列化程序,则只需添加如下所示的 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(拥有 Dependent 作用域)bean,或者在极端情况下,提供一个类型为 jakarta.json.bind.Jsonb(拥有 Singleton 作用域)的 bean。如果使用后一种方法,则非常重要的手动注入并应用 CDI 生成器中生成 jakarta.json.bind.Jsonb 的所有 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 扩展。

Table 1. Table Context object
GAV Usage

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": "http://localhost:8081/records"
                    },
                    "first": {
                        "href": "http://localhost:8081/records/first"
                    }
                }
            },
            {
                "id": 2,
                "slug": "second",
                "value": "Second value",
                "_links": {
                    "list": {
                        "href": "http://localhost:8081/records"
                    },
                    "first": {
                        "href": "http://localhost:8081/records/first"
                    }
                }
            }
        ]
    },
    "_links": {
        "list": {
            "href": "http://localhost: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": "http://localhost:8081/records"
        },
        "first": {
            "href": "http://localhost:8081/records/first"
        }
    }
}

Creating a frontend

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

你现在可以与你的 REST 服务进行交互:

Building a native executable

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

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

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

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

About serialization

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 将无法自动注册所需的类中的反射。

这将我们带到下一部分。

Using 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 文件,其中包含来自此 $${quickstarts-base-url}/blob/main/rest-json-quickstart/src/main/resources/META-INF/resources/legumes.html [legumes.html] 文件的内容。

打开一个浏览器到 [role="bare"][role="bare"]http://localhost:8080/legumes.html,您将看到我们的豆类列表。

当以本地可执行文件方式运行应用程序时,就会开始有趣的部分:

  • 使用以下命令创建本地可执行文件:include::./_includes/devtools/build-native.adoc[]

  • execute it with ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner

  • 打开浏览器并转到 [role="bare"][role="bare"]http://localhost:8080/legumes.html

那里没有豆类。

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

目前,当 JSON-B 或 Jackson 尝试获取某个类的字段列表时,如果未注册类以进行反射,则不会引发异常。GraalVM 将返回一个空的字段列表。 希望将来会改变这种情况,并使错误更明显。

我们可以通过在 Legume 类中添加 @RegisterForReflection 注解手动注册 Legume 以进行反射:

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection
public class Legume {
    // ...
}

@RegisterForReflection 注释指示 Quarkus 在本机编译期间保留类及其成员。有关 @RegisterForReflection 注释的更多详细信息,请参阅 native application tips 页面。

让我们执行该操作,并按照之前相同的步骤进行:

  • 点击 Ctrl+C 以停止应用程序

  • 使用以下命令创建本地可执行文件:include::./_includes/devtools/build-native.adoc[]

  • execute it with ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner

  • 打开浏览器并转到 [role="bare"][role="bare"]http://localhost:8080/legumes.html

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

Being reactive

对于反应性工作负载,请始终使用 Quarkus REST

你可以返回 reactive types 以处理异步处理。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 - an intuitive reactive programming library 中找到有关 Mutiny 的更多详细信息。

HTTP filters and interceptors

可以通过分别提供 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 filter

Cross-origin resource sharing (CORS)是一种机制,可以通过第一个资源提供的域之外的其他域请求网页上受限的资源。

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

GZip Support

Quarkus 附带 GZip 支持(尽管默认情况下未启用)。以下配置控件允许配置 GZip 支持。

quarkus.resteasy.gzip.enabled=true (1)
quarkus.resteasy.gzip.max-input=10M (2)
1 Enable Gzip support.
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 Support

RESTEasy 通过 RESTEasy Multipart Provider 支持多部分。

Quarkus 提供了一个名为 quarkus-resteasy-multipart 的扩展,以便为您简化操作。

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

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

Unresolved include directive in modules/ROOT/pages/resteasy.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-resteasy-multipart.adoc[]

Servlet compatibility

在 Quarkus 中,RESTEasy 既可以在 Vert.x HTTP 服务器上直接运行,也可以在任何 servlet 依赖上运行 Undertow。

因此,某些类,例如 HttpServletRequest 不总是可用于注入。对于此特定类的多数用例,除了获取远程客户端的 IP,Jakarta REST 等价项涵盖了这些用例。

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

RESTEasy and REST Client interactions

在 Quarkus 中,RESTEasy 扩展和 the REST Client extension 共享相同的基础结构。此考虑事项的一个重要后果是它们共享相同的提供程序列表(在 Jakarta REST 的含义中)。

例如,如果您声明一个 WriterInterceptor,默认情况下,它将拦截服务器调用和客户端调用,而这可能不是所需的行为。

但是,您可以更改此默认行为,并将一个提供程序约束为:

  • 通过向您的提供程序添加 @ConstrainedTo(RuntimeType.SERVER) 注释来仅考虑 server 调用;

  • 通过向您的提供程序添加 @ConstrainedTo(RuntimeType.CLIENT) 注释来仅考虑 client 调用。

What’s Different from Jakarta EE Development

No Need for Application Class

支持应用提供的 Application 子类的配置,但不是必需的。

Only a single Jakarta REST application

与运行于标准 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 定义的应用路径访问(默认: /)。

Support limitations of Jakarta REST application

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

Lifecycle of Resources

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

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

如果设置成 true(默认),则会创建一个资源类的 single instance 来服务所有请求(如 @jakarta.inject.Singleton 所定义)。

如果设置成 false,则会为每个请求创建一个资源类的 new instance

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

Include/Exclude Jakarta REST classes with build time conditions

借助于构建时间条件(与 CDI bean 中的情况相同),Quarkus 能够直接包含或排除 Jakarta REST 资源、提供程序和功能。因此,可以使用配置文件条件 (@io.quarkus.arc.profile.IfBuildProfile@io.quarkus.arc.profile.UnlessBuildProfile) 和/或属性条件 (io.quarkus.arc.properties.IfBuildPropertyio.quarkus.arc.properties.UnlessBuildProperty) 对各种 Jakarta REST 类进行注释,以便在构建期间向 Quarkus 指示应包含哪些 Jakarta REST 类。

在以下示例中,Quarkus 仅当启用了构建配置文件 app1 时,才会包含端点 sayHello

@IfBuildProfile("app1")
public class ResourceForApp1Only {

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

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

Conclusion

借助于经过验证且广为人知的技术,使用 Quarkus 创建 JSON REST 服务非常容易。

像往常一样,当作为本机可执行文件运行你的应用程序时,Quarkus 会进一步简化底层工作。

这里只有一点需要注意:如果您使用 Response,并且 Quarkus 无法确定所序列化的 Bean,则需要使用 `@RegisterForReflection`对它们进行注释。