RESTEasy Classic
本指南关于 RESTEasy Classic,它曾经是 Quarkus 2.8 之前的默认 Jakarta REST(以前称为 JAX-RS)实现。
This guide is about RESTEasy Classic, which used to be the default Jakarta REST (formerly known as JAX-RS) implementation until Quarkus 2.8.
现在建议使用 Quarkus REST(以前称为 RESTEasy Reactive),它对传统阻塞工作负载和响应式工作负载提供同样出色的支持。
It is now recommended to use Quarkus REST (formerly RESTEasy Reactive), which supports both traditional blocking workloads and reactive workloads equally well.
有关 Quarkus REST 的更多信息,请参阅 introductory REST JSON guide 或 Quarkus REST reference documentation。
For more information about Quarkus REST, please see the introductory REST JSON guide or the Quarkus REST reference documentation.
如果您需要 REST client based on RESTEasy Classic,还有另一本指南(包括对 JSON 的支持)。 |
There is another guide if you need a REST client based on RESTEasy Classic (including support for JSON). |
Architecture
本指南中创建的应用程序非常简单:用户可以通过表单向列表中添加元素,并将相应地更新列表。
The application created in this guide is straightforward: users can add elements to a list through a form, and the list gets updated accordingly.
浏览器和服务器之间所有信息都采用 JSON 格式。
All the information between the browser and the server is formatted as JSON.
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in resteasy.adoc - include::{includes}/devtools/create-app.adoc[]
此命令生成一个导入 RESTEasy/Jakarta REST 和 Jackson 扩展的新项目,特别添加了以下依赖项:
This command generates a new project importing the RESTEasy/Jakarta REST and Jackson extensions, and in particular, adds the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-resteasy-jackson")
为了改善用户体验,Quarkus 注册了三个 Jackson Java 8 modules,因此您无需手动进行此操作。 To improve user experience, Quarkus registers the three Jackson Java 8 modules, so you do not need to do it manually. |
Quarkus 还支持 JSON-B,因此,如果您更喜欢 JSON-B 而不是 Jackson,则可以创建依赖于 RESTEasy JSON-B 扩展的项目:
Quarkus also supports JSON-B, so if you prefer JSON-B over Jackson, you can create a project relying on the RESTEasy JSON-B extension instead:
Unresolved directive in resteasy.adoc - include::{includes}/devtools/create-app.adoc[]
此命令生成一个导入 RESTEasy/Jakarta REST 和 JSON-B 扩展的新项目,特别添加了以下依赖项:
This command generates a new project importing the RESTEasy/Jakarta REST and JSON-B extensions, and in particular, adds the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
implementation("io.quarkus:quarkus-resteasy-jsonb")
Creating the first JSON REST service
在此示例中,我们将创建一个应用程序来管理水果列表。
In this example, we will create an application to manage a list of fruits.
首先,让我们按如下方式创建 Fruit
bean:
First, let us create the Fruit
bean as follows:
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 序列化层需要有默认构造函数。
Nothing fancy. One important thing to note is that having a default constructor is required by the JSON serialization layer.
现在,通过以下方式创建 org.acme.rest.json.FruitResource
类:
Now, create the org.acme.rest.json.FruitResource
class as follows:
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 注解定义您的端点。
The implementation is pretty straightforward, and you just need to define your endpoints using the Jakarta REST annotations.
The Fruit
objects will be automatically serialized/deserialized by JSON-B or Jackson,
depending on the extension you chose when initializing the project.
当安装了如 When a JSON extension like 若要禁用默认 JSON 行为,请设置 To disable the default JSON behavior, set 如果您不依赖于 JSON 默认值,强烈建议在端点上使用 If you don’t depend on the JSON default, it’s highly advisable to use |
Configuring JSON support
Jackson
在 Quarkus 中,通过 CDI(由 Quarkus 扩展使用)获得的默认 Jackson ObjectMapper
设置为忽略未知属性(通过禁用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
)。
In Quarkus, the default Jackson ObjectMapper
obtained via CDI (utilized by Quarkus extensions) is set to ignore unknown properties (by disabling DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
).
若要恢复 Jackson 的默认行为,请在 application.properties
中设置 quarkus.jackson.fail-on-unknown-properties=true
,或使用 @JsonIgnoreProperties(ignoreUnknown = false)
为每个类进行设置。
To revert to Jackson’s default behavior, set quarkus.jackson.fail-on-unknown-properties=true
in your application.properties
, or set it on a per-class basis with @JsonIgnoreProperties(ignoreUnknown = false)
.
此外,ObjectMapper
以 ISO-8601 格式化日期和时间(通过禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
)。
Additionally, the ObjectMapper
formats dates and times in ISO-8601 (by disabling SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
).
若要恢复 Jackson 的默认行为,请在 application.properties
中使用 quarkus.jackson.write-dates-as-timestamps=true
。对于单个字段上的自定义日期格式,请使用 @JsonFormat
注解。
To restore Jackson’s default behavior, use quarkus.jackson.write-dates-as-timestamps=true
in your application.properties
.
For custom date format on a single field, use the @JsonFormat
annotation.
Quarkus 通过 CDI bean 简化了 Jackson 配置。创建类型为 io.quarkus.jackson.ObjectMapperCustomizer
的 CDI bean,以应用各种 Jackson 设置。以下是注册自定义模块的一个示例:
Quarkus simplifies Jackson configuration via CDI beans. Create a CDI bean of type io.quarkus.jackson.ObjectMapperCustomizer
to apply various Jackson settings. Here’s an example for registering a custom module:
@ApplicationScoped
public class MyObjectMapperCustomizer implements ObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
// Add custom Jackson configuration here
}
}
推荐采用此方法来配置 Jackson 设置。
This approach is recommended for configuring Jackson settings.
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 的定制功能。
Users can even provide their own ObjectMapper
bean if they so choose.
If this is done, it is very important to manually inject and apply all io.quarkus.jackson.ObjectMapperCustomizer
beans in the CDI producer that produces ObjectMapper
.
Failure to do so will prevent Jackson-specific customizations provided by various extensions from being applied.
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 的选项。
As stated above, Quarkus provides the option of using JSON-B instead of Jackson via the use of the quarkus-resteasy-jsonb
extension.
按照前一节中描述的相同方法,可以使用 io.quarkus.jsonb.JsonbConfigCustomizer
bean 配置 JSON-B。
Following the same approach described in the previous section, JSON-B can be configured using an io.quarkus.jsonb.JsonbConfigCustomizer
bean.
例如,如果需要使用 JSON-B 注册名为 FooSerializer
的类型 com.example.Foo
的自定义序列化程序,则只需添加如下所示的 bean:
If, for example, a custom serializer named FooSerializer
for type com.example.Foo
needs to be registered with JSON-B, the addition of a bean like the following would suffice:
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 特定自定义。
A more advanced option would be to directly provide a bean of jakarta.json.bind.JsonbConfig
(with a Dependent
scope) or, in the extreme case, to provide a bean of type jakarta.json.bind.Jsonb
(with a Singleton
scope).
If the latter approach is leveraged it is very important to manually inject and apply all io.quarkus.jsonb.JsonbConfigCustomizer
beans in the CDI producer that produces jakarta.json.bind.Jsonb
.
Failure to do so will prevent JSON-B specific customizations provided by various extensions from being applied.
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;
}
}
JSON Hypertext Application Language (HAL) support
HAL 标准是用于表示 Web 链接的简单格式。
The HAL standard is a simple format to represent web links.
要启用 HAL 支持,请将 quarkus-hal
扩展添加到您的项目。此外,由于 HAL 需要 JSON 支持,因此您需要添加 quarkus-resteasy-jsonb
或 quarkus-resteasy-jackson
扩展。
To enable the HAL support, add the quarkus-hal
extension to your project. Also, as HAL needs JSON support, you need to add either the quarkus-resteasy-jsonb
or the quarkus-resteasy-jackson
extension.
GAV | Usage |
---|---|
|
添加扩展后,我们现在可以对 REST 资源进行注释以生成媒体类型 application/hal+json
(或使用 RestMediaType.APPLICATION_HAL_JSON)。例如:
After adding the extensions, we can now annotate the REST resources to produce the media type application/hal+json
(or use RestMediaType.APPLICATION_HAL_JSON). For example:
@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
将接受媒体类型 json
和 hal+json
,以 Hal 格式打印记录。
Now, the endpoints /records
and /records/first
will accept the media type, both json
and hal+json
, to print the records in Hal format.
例如,如果我们使用 curl 调用 /records
端点以返回记录列表,则 HAL 格式如下所示:
For example, if we invoke the /records
endpoint using curl to return a list of records, the HAL format will look like as follows:
& 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
时,输出内容为:
When we call a resource /records/first
that returns only one instance, then the output is:
& 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 文件的内容。
Now let us add a simple web page to interact with our FruitResource
.
Quarkus automatically serves static resources located under the META-INF/resources
directory.
In the src/main/resources/META-INF/resources
directory, add a fruits.html
file with the content from this fruits.html file in it.
你现在可以与你的 REST 服务进行交互:
You can now interact with your REST service:
-
start Quarkus with:include::{includes}/devtools/dev.adoc[]
-
open a browser to
http://localhost:8080/fruits.html
-
add new fruits to the list via the form
Building a native executable
您可以使用以下常用命令构建一个本机可执行文件:
You can build a native executable with the usual command:
Unresolved directive in resteasy.adoc - include::{includes}/devtools/build-native.adoc[]
运行它与执行 ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner
一样简单。
Running it is as simple as executing ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner
.
然后您可以将浏览器指向 http://localhost:8080/fruits.html
,并使用您的应用程序。
You can then point your browser to http://localhost:8080/fruits.html
and use your application.
About serialization
JSON 序列化库使用 Java 反射来获取对象的属性并对其进行序列化。
JSON serialization libraries use Java reflection to get the properties of an object and serialize them.
在使用带有 GraalVM 的原生可执行文件时,需要注册将与反射一起使用的所有类。好消息是 Quarkus 大多数情况下都会为您完成这项工作。到目前为止,我们尚未注册任何类,甚至没有为反射使用注册 Fruit
,并且一切工作正常。
When using native executables with GraalVM, all classes that will be used with reflection need to be registered.
The good news is that Quarkus does that work for you most of the time.
So far, we have not registered any class, not even Fruit
, for reflection usage, and everything is working fine.
当 Quarkus 能够从 REST 方法推断出序列化类型时,它会执行一些神奇操作。当您有以下 REST 方法时,Quarkus 确定 Fruit
将被序列化:
Quarkus performs some magic when it is capable of inferring the serialized types from the REST methods.
When you have the following REST method, Quarkus determines that Fruit
will be serialized:
@GET
public List<Fruit> list() {
// ...
}
Quarkus 在构建时通过分析 REST 方法自动为您完成此操作,这就是为什么我们在本指南的第一部分中不需要任何反射注册的原因。
Quarkus does that for you automatically by analyzing the REST methods at build time, and that is why we did not need any reflection registration in the first part of this guide.
Jakarta REST 世界中的另一个常见模式是使用 Response
对象。Response
带有一些优点:
Another common pattern in the Jakarta REST world is to use the Response
object.
Response
comes with some nice perks:
-
You can return different entity types depending on what happens in your method (a
Legume
or anError
for instance). -
You can set the attributes of the
Response
(the status comes to mind in the case of an error).
您的 REST 方法看起来像这样:
Your REST method then looks like this:
@GET
public Response list() {
// ...
}
Quarkus 无法在构建时确定 Response
中包含的类型,因为该信息不可用。在这种情况下,Quarkus 将无法自动注册所需的类中的反射。
Quarkus cannot determine at build time the type included in the Response
as the information is not available.
In this case, Quarkus won’t be able to register for reflection in the required classes automatically.
这将我们带到下一部分。
This leads us to our next section.
Using response
让我们创建 Legume
类,它将被序列化为 JSON,遵循与我们的 Fruit
类相同的模型:
Let us create the Legume
class, which will be serialized as JSON, following the same model as for our Fruit
class:
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 服务。
Now let’s create a LegumeResource
REST service with only one method that returns the list of legumes.
此方法返回 Response
,而不是 Legume
的列表。
This method returns a Response
and not a list of 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-blob-url}/rest-json-quickstart/src/main/resources/META-INF/resources/legumes.html [legumes.html] 文件的内容。
Now, let us add a simple web page to display our list of legumes.
In the src/main/resources/META-INF/resources
directory, add a legumes.html
file with the content from this
{quickstarts-blob-url}/rest-json-quickstart/src/main/resources/META-INF/resources/legumes.html[legumes.html] file in it.
打开一个浏览器到 [role="bare"][role="bare"]http://localhost:8080/legumes.html,您将看到我们的豆类列表。
Open a browser to [role="bare"]http://localhost:8080/legumes.html, and you will see our list of legumes.
当以本地可执行文件方式运行应用程序时,就会开始有趣的部分:
The interesting part starts when running the application as a native executable:
-
create the native executable with:include::{includes}/devtools/build-native.adoc[]
-
execute it with
./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner
-
open a browser and go to [role="bare"]http://localhost:8080/legumes.html
那里没有豆类。
There are no legumes there.
如上所述,问题在于 Quarkus 无法通过分析 REST 端点来确定 Legume
类,它将需要一些反射。JSON 序列化库尝试获取 Legume
字段的列表并获取一个空列表,因此它不会序列化字段数据。
As mentioned above, the issue is that Quarkus could not determine the Legume
class, which will require some reflection by analyzing the REST endpoints.
The JSON serialization library tries to get the list of fields of Legume
and gets an empty list, so it does not serialize the fields' data.
目前,当 JSON-B 或 Jackson 尝试获取某个类的字段列表时,如果未注册类以进行反射,则不会引发异常。GraalVM 将返回一个空的字段列表。 At the moment, when JSON-B or Jackson tries to get the list of fields of a class, if the class is not registered for reflection, no exception will be thrown. GraalVM will return an empty list of fields. 希望将来会改变这种情况,并使错误更明显。 Hopefully, this will change in the future and make the error more obvious. |
我们可以通过在 Legume
类中添加 @RegisterForReflection
注解手动注册 Legume
以进行反射:
We can register Legume
for reflection manually by adding the @RegisterForReflection
annotation on our Legume
class:
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class Legume {
// ...
}
|
The |
让我们执行该操作,并按照之前相同的步骤进行:
Let us do that and follow the same steps as before:
-
hit
Ctrl+C
to stop the application -
create the native executable with:include::{includes}/devtools/build-native.adoc[]
-
execute it with
./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner
-
open a browser and go to [role="bare"]http://localhost:8080/legumes.html
这一次,你可以看到我们的豆类列表。
This time, you can see our list of legumes.
Being reactive
对于反应性工作负载,请始终使用 Quarkus REST。
For reactive workloads, please always use Quarkus REST.
你可以返回 reactive types 以处理异步处理。Quarkus 建议使用 Mutiny 来编写反应式和异步代码。
You can return reactive types to handle asynchronous processing. Quarkus recommends the usage of Mutiny to write reactive and asynchronous code.
要集成 Mutiny 和 RESTEasy,您需要将 quarkus-resteasy-mutiny
依赖项添加到您的项目中:
To integrate Mutiny and RESTEasy, you need to add the quarkus-resteasy-mutiny
dependency to your project:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-mutiny</artifactId>
</dependency>
implementation("io.quarkus:quarkus-resteasy-mutiny")
然后,您的终结点可以返回 Uni
或 Multi
实例:
Then, your endpoint can return Uni
or Multi
instances:
@GET
@Path("/{name}")
public Uni<Fruit> getOne(@PathParam String name) {
return findByName(name);
}
@GET
public Multi<Fruit> getAll() {
return findAll();
}
当你有单个结果时,使用 Uni
。当你有可能异步发出的多个项时,使用 Multi
。
Use Uni
when you have a single result.
Use Multi
when you have multiple items that may be emitted asynchronously.
你可以使用 Uni
和 Response
返回异步 HTTP 响应:Uni<Response>
。
You can use Uni
and Response
to return asynchronous HTTP responses: Uni<Response>
.
可在 Mutiny - an intuitive reactive programming library 中找到有关 Mutiny 的更多详细信息。
More details about Mutiny can be found in Mutiny - an intuitive reactive programming library.
HTTP filters and interceptors
可以通过分别提供 ContainerRequestFilter
或 ContainerResponseFilter
实现拦截 HTTP 请求和响应。这些过滤器适用于处理与消息关联的元数据:HTTP 标头、查询参数、媒体类型和其他元数据。它们还可以中止请求处理,例如,当用户没有权访问终结点时。
Both HTTP request and response can be intercepted by providing ContainerRequestFilter
or ContainerResponseFilter
implementations respectively.
These filters are suitable for processing the metadata associated with a message: HTTP headers, query parameters, media type, and other metadata.
They also can abort the request processing, for instance, when the user does not have permission to access the endpoint.
让我们使用 ContainerRequestFilter
为我们的服务添加日志记录功能。我们可以通过实施 ContainerRequestFilter
并用 @Provider
注释对其进行注释来做到这一点:
Let’s use ContainerRequestFilter
to add logging capability to our service. We can do that by implementing ContainerRequestFilter
and annotating it with the @Provider
annotation:
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 方法时,请求都会记录到控制台中:
Now, whenever a REST method is invoked, the request will be logged into the console:
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)是一种机制,可以通过第一个资源提供的域之外的其他域请求网页上受限的资源。
Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.
Quarkus 在 HTTP 层级别包含了一个 CORS 过滤器。有关 CORS 过滤器及其用法的更多信息,请参阅 Quarkus “跨源资源共享”指南的 CORS filter 部分。
Quarkus includes a CORS filter at the HTTP layer level. For more information about the CORS filters and their usage, see the CORS filter section of the Quarkus "Cross-origin resource sharing" guide.
GZip Support
Quarkus 附带 GZip 支持(尽管默认情况下未启用)。以下配置控件允许配置 GZip 支持。
Quarkus comes with GZip support (even though it is not enabled by default). The following configuration knobs allow to configure GZip support.
quarkus.resteasy.gzip.enabled=true (1)
quarkus.resteasy.gzip.max-input=10M (2)
1 | Enable Gzip support. |
2 | Configure the upper limit on the deflated request body.
This is useful to mitigate potential attacks by limiting their reach. The default value is 10M .
This configuration option would recognize strings in this format (shown as a regular expression): [0-9]+[KkMmGgTtPpEeZzYy]? .
If no suffix is given, assume bytes. |
启用 GZip 支持后,您可以通过将 @org.jboss.resteasy.annotations.GZIP
注释添加到终结点方法来在终结点上使用它。
Once GZip support has been enabled, you can use it on an endpoint by adding the @org.jboss.resteasy.annotations.GZIP
annotation to your endpoint method.
还有一个 |
There is also the |
Multipart Support
RESTEasy 通过 RESTEasy Multipart Provider 支持多部分。
RESTEasy supports multipart via the RESTEasy Multipart Provider.
Quarkus 提供了一个名为 quarkus-resteasy-multipart
的扩展,以便为您简化操作。
Quarkus provides an extension called quarkus-resteasy-multipart
to make things easier for you.
此扩展与 RESTEasy 默认行为略有不同,因为默认字符集(如果您的请求中未指定任何字符集)是 UTF-8 而不是 US-ASCII。
This extension slightly differs from the RESTEasy default behavior as the default charset (if none is specified in your request) is UTF-8 rather than US-ASCII.
您可以使用以下配置属性配置此行为:
You can configure this behavior with the following configuration properties:
Unresolved directive in resteasy.adoc - include::{generated-dir}/config/quarkus-resteasy-multipart.adoc[]
Servlet compatibility
在 Quarkus 中,RESTEasy 既可以在 Vert.x HTTP 服务器上直接运行,也可以在任何 servlet 依赖上运行 Undertow。
In Quarkus, RESTEasy can either run directly on top of the Vert.x HTTP server, or on top of Undertow if you have any servlet dependency.
因此,某些类,例如 HttpServletRequest
不总是可用于注入。对于此特定类的多数用例,除了获取远程客户端的 IP,Jakarta REST 等价项涵盖了这些用例。
As a result, certain classes, such as HttpServletRequest
are not always available for injection.
Most use cases for this particular class are covered by Jakarta REST equivalents, except for getting the remote client’s IP.
RESTEasy 提供了一个替换的 API,您可以注入它: HttpRequest
,它包含方法 getRemoteAddress()
和 getRemoteHost()
来解决此问题。
RESTEasy comes with a replacement API that you can inject:
HttpRequest
, which has the methods
getRemoteAddress()
and getRemoteHost()
to solve this problem.
RESTEasy and REST Client interactions
在 Quarkus 中,RESTEasy 扩展和 the REST Client extension 共享相同的基础结构。此考虑事项的一个重要后果是它们共享相同的提供程序列表(在 Jakarta REST 的含义中)。
In Quarkus, the RESTEasy extension and the REST Client extension share the same infrastructure. One important consequence of this consideration is that they share the same list of providers (in the Jakarta REST meaning of the word).
例如,如果您声明一个 WriterInterceptor
,默认情况下,它将拦截服务器调用和客户端调用,而这可能不是所需的行为。
For instance, if you declare a WriterInterceptor
, it will, by default, intercept both the servers calls and the client calls,
which might not be the desired behavior.
但是,您可以更改此默认行为,并将一个提供程序约束为:
However, you can change this default behavior and constrain a provider to:
-
only consider server calls by adding the
@ConstrainedTo(RuntimeType.SERVER)
annotation to your provider; -
only consider client calls by adding the
@ConstrainedTo(RuntimeType.CLIENT)
annotation to your provider.
What’s Different from Jakarta EE Development
No Need for Application
Class
支持应用提供的 Application
子类的配置,但不是必需的。
Configuration via an application-supplied subclass of Application
is supported but not required.
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
。
In contrast to Jakarta REST (and RESTeasy) running in a standard servlet container, Quarkus only supports the deployment of a single Jakarta REST application.
If multiple Jakarta REST Application
classes are defined, the build will fail with the message Multiple classes have been annotated with @ApplicationPath which is currently not supported
.
如果定义了多个 Jakarta REST 应用,则可以使用属性 quarkus.resteasy.ignore-application-classes=true
忽略所有显式的 Application
类。这会使所有资源类可通过 quarkus.resteasy.path
定义的应用路径访问(默认: /
)。
If multiple Jakarta REST applications are defined, the property quarkus.resteasy.ignore-application-classes=true
can be used to ignore all explicit Application
classes.
This makes all resource-classes available via the application-path as defined by quarkus.resteasy.path
(default: /
).
Support limitations of Jakarta REST application
RESTEasy 扩展不支持类 jakarta.ws.rs.core.Application
的方法 getProperties()
。此外,它仅依赖方法 getClasses()
和 getSingletons()
过滤带注释的资源、提供程序和特性类。它不会过滤内置资源、提供程序和特性类以及其他扩展注册的资源、提供程序和特性类。最后,会忽略方法 getSingletons()
返回的对象,仅考虑这些类来过滤资源、提供程序和特性类,换句话说,方法 getSingletons()
的管理方式与 getClasses()
相同。
The RESTEasy extension doesn’t support the method getProperties()
of the class jakarta.ws.rs.core.Application
.
Moreover, it only relies on the methods getClasses()
and getSingletons()
to filter out the annotated resource, provider, and feature classes.
It does not filter out the built-in resource, provider, and feature classes and also the resource, provider, and feature classes registered by the other extensions.
Finally, the objects returned by the method getSingletons()
are ignored, only the classes are taken into account to filter out the resource, provider and feature classes, in other words the method getSingletons()
is managed the same way as getClasses()
.
Lifecycle of Resources
在 Quarkus 中,所有 Jakarta REST 资源都被视为 CDI bean。可能通过 @Inject
注入其他 bean,使用如 @Transactional
这样的绑定绑定拦截器,定义 @PostConstruct
回调等。
In Quarkus, all Jakarta REST resources are treated as CDI beans.
It’s possible to inject other beans via @Inject
, bind interceptors using bindings such as @Transactional
, define @PostConstruct
callbacks, etc.
如果没有在资源类上声明作用域注释,则该作用域将默认为该作用域。属性 quarkus.resteasy.singleton-resources
可以控制默认作用域。
If no scope annotation is declared on the resource class, then the scope is defaulted.
The quarkus.resteasy.singleton-resources
property can control the default scope.
如果设置成 true
(默认),则会创建一个资源类的 single instance 来服务所有请求(如 @jakarta.inject.Singleton
所定义)。
If set to true
(default), then a single instance of a resource class is created to service all requests (as defined by @jakarta.inject.Singleton
).
如果设置成 false
,则会为每个请求创建一个资源类的 new instance。
If set to false
, then a new instance of the resource class is created per each request.
显式的 CDI 作用域注释(@RequestScoped
、@ApplicationScoped
等)始终覆盖默认行为并指定资源实例的生命周期。
An explicit CDI scope annotation (@RequestScoped
, @ApplicationScoped
, etc.) always overrides the default behavior and specifies the lifecycle of resource instances.
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.IfBuildProperty
或 io.quarkus.arc.properties.UnlessBuildProperty
) 对各种 Jakarta REST 类进行注释,以便在构建期间向 Quarkus 指示应包含哪些 Jakarta REST 类。
Quarkus enables the inclusion or exclusion of Jakarta REST Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans.
Thus, the various Jakarta REST classes can be annotated with profile conditions (@io.quarkus.arc.profile.IfBuildProfile
or @io.quarkus.arc.profile.UnlessBuildProfile
) and/or with property conditions (io.quarkus.arc.properties.IfBuildProperty
or io.quarkus.arc.properties.UnlessBuildProperty
) to indicate to Quarkus at build time under which conditions these Jakarta REST classes should be included.
在以下示例中,Quarkus 仅当启用了构建配置文件 app1
时,才会包含端点 sayHello
。
In the following example, Quarkus includes the endpoint sayHello
if and only if the build profile app1
has been enabled.
@IfBuildProfile("app1")
public class ResourceForApp1Only {
@GET
@Path("sayHello")
public String sayHello() {
return "hello";
}
}
请注意,如果已检测到 Jakarta REST 应用程序并且已覆盖了 getClasses()
和/或 getSingletons()
方法,Quarkus 将忽略构建时间条件,并且仅考虑在 Jakarta REST 应用程序中定义的内容。
Please note that if a Jakarta REST Application has been detected and the method getClasses()
and/or getSingletons()
has/have been overridden, Quarkus will ignore the build time conditions and consider only what has been defined in the Jakarta REST Application.
Conclusion
借助于经过验证且广为人知的技术,使用 Quarkus 创建 JSON REST 服务非常容易。
Creating JSON REST services with Quarkus is easy as it relies on proven and well-known technologies.
像往常一样,当作为本机可执行文件运行你的应用程序时,Quarkus 会进一步简化底层工作。
As usual, Quarkus further simplifies things under the hood when running your application as a native executable.
这里只有一点需要注意:如果您使用 Response
,并且 Quarkus 无法确定所序列化的 Bean,则需要使用 `@RegisterForReflection`对它们进行注释。
There is only one thing to remember: if you use Response
and Quarkus cannot determine the beans that are serialized, you need to annotate them with @RegisterForReflection
.