编写JSON REST服务
JSON现在是微服务之间的 通用 语言。
在本指南中,我们将了解如何使用REST服务和生成有效的JSON。
there is another guide if you need a REST client (including support for JSON). |
This is an introduction to writing JSON REST services with Quarkus. A more detailed guide about Quarkus REST (formerly RESTEasy Reactive) is available here. |
先决条件
完成这个指南,你需要:
-
大概15分钟
-
编辑器
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
如果你愿意的话,还可以选择使用Quarkus CLI
-
如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)
解决方案
我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。
克隆 Git 仓库。 git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或者下载一个 存档 。
The solution is located in the rest-json-quickstart
directory.
创建Maven项目
首先,我们需要一个新的项目。用以下命令创建一个新项目:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=rest-json-quickstart"
This command generates a new project importing the Quarkus REST/Jakarta REST and Jackson extensions, and in particular adds the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jackson")
为了提高用户体验,Quarkus注册了三个Jackson Java 8模块 ,所以你不需要手动操作。 |
Quarkus also supports JSON-B so, if you prefer JSON-B over Jackson, you can create a project relying on the Quarkus REST JSON-B extension instead:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=rest-json-quickstart"
This command generates a new project importing the Quarkus REST/Jakarta REST and JSON-B extensions, and in particular adds the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jsonb</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jsonb")
If you use JSON-B and JSON-P, make sure you don’t use the shortcut methods offered by At the moment, any single call to these methods will initialize a new You can import it as a static import to simplify your code:
|
For more information about Quarkus REST, please refer to the dedicated guide. |
创建你的第一个JSON REST服务
在这个例子中,我们将创建一个应用程序来管理一个fruit列表。
首先,让我们创建 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;
}
}
The implementation is pretty straightforward, and you just need to define your endpoints using the Jakarta REST annotations.
When a JSON extension is installed such as |
配置JSON支持
Jackson
在Quarkus中,通过CDI获得的默认Jackson ObjectMapper
(并由Quarkus扩展使用)被配置为忽略未知属性(通过禁用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
功能)。
You can restore the default behavior of Jackson by setting quarkus.jackson.fail-on-unknown-properties=true
in your application.properties
or on a per-class basis via @JsonIgnoreProperties(ignoreUnknown = false)
.
此外, ObjectMapper
被配置为ISO-8601的日期和时间格式(通过禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
功能)。
Jackson的默认行为可以通过在你的 application.properties
中设置 quarkus.jackson.write-dates-as-timestamps=true
来设置。如果你想改变单个字段的默认行为,你可以使用 @JsonFormat
注解。
另外,Quarkus使得通过CDI beans来配置各种Jackson设置变得非常容易。最简单的(也是建议的)方法是定义一个类型为 io.quarkus.jackson.ObjectMapperCustomizer
的CDI Bean,在其中可以使用任何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
@Produces
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;
}
}
Mixin support
Quarkus automates the registration of Jackson’s Mixin support, via the io.quarkus.jackson.JacksonMixin
annotation.
This annotation can be placed on classes that are meant to be used as Jackson mixins while the classes they are meant to customize
are defined as the value of the annotation.
JSON-B
如上所述,Quarkus通过使用 quarkus-resteasy-jsonb
扩展提供了使用JSON-B而不是Jackson的选项。
按照上一节所述的相同方法,JSON-B可以使用 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());
}
}
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;
}
}
创建一个前台
Now let’s 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服务进行交互:
-
启动Quarkus:
CLIquarkus dev
Maven./mvnw quarkus:dev
Gradle./gradlew --console=plain quarkusDev
-
通过表格添加新的fruits到列表中
构建一个本地可执行文件
你可以使用常用命令构建本机可执行文件:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
运行它就像执行 ./target/rest-json-quickstart-1.0.0-SNAPSHOT-runner
一样简单。
然后你可以将你的浏览器访问 http://localhost:8080/fruits.html
并使用你的应用程序。
关于序列化
JSON序列化库使用Java反射来获取一个对象的属性并将其序列化。
当使用GraalVM的本地可执行文件时,需要注册所有将与反射一起使用的类。好消息是,Quarkus在大多数时候都会为你做这项工作。到目前为止,我们还没有注册任何类,甚至没有注册 Fruit
,并且一切正常。
当Quarkus能够从REST方法中推断出序列化的类型时,它会发挥一些作用。当你有以下的REST方法时,Quarkus确定 Fruit
将被序列化:
@GET
public List<Fruit> list() {
// ...
}
Quarkus通过在构建时分析REST方法自动为你执行此操,这就是为什么我们在本指南的第一部分不需要任何反射注册。
Another common pattern in the Jakarta REST world is to use the Response
object.
Response
comes with some nice perks:
-
你可以根据你的方法中发生的情况,返回不同的实体类型(例如,
Legume
或Error
)。 -
你可以设置
Response
的属性(在出现错误的情况下,会想到状态)。
你的 REST 方法如下所示:
@GET
public Response list() {
// ...
}
Quarkus不可能在构建时确定 Response
中包含的类型,因为该信息不可用。在这种情况下,Quarkus将无法自动注册反映所需的类。
这将我们引向下一节。
使用Response
让我们创建一个将被序列化为 JSON 的 Legume
类,遵循与我们的 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服务,它只有一个返回legumes类列表的方法。
该方法返回一个 Response
,而不是一个 Legume
列表。
package org.acme.rest.json;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
@Path("/legumes")
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();
}
}
现在让我们添加一个简单的网页来显示我们的legumes列表。在 src/main/resources/META-INF/resources
目录中,添加一个 legumes.html
文件,其中包含这个https://github.com/quarkusio/quarkus-quickstarts/blob/main/rest-json-quickstart/src/main/resources/META-INF/resources/legumes.html[legumes.html] 文件的内容。
Open a browser to http://localhost:8080/legumes.html, and you will see our list of legumes.
有趣的部分是在将应用程序作为本机可执行文件运行时开始的:
-
创建本地可执行文件。
CLIquarkus 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/legumes.html
那里没有legumes。
As mentioned above, the issue is that Quarkus was not able to determine the Legume
class 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将简单地返回一个空的字段列表。 希望这在将来会有所改变,使错误更加明显。 |
我们可以通过在我们的 Legume
类上添加 @RegisterForReflection
注解来手动注册 Legume
进行反射:
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class Legume {
// ...
}
@RegisterForReflection 注解指示Quarkus在本地编译过程中保留该类和其成员。关于 @RegisterForReflection 注解的更多细节,请访问 本地应用程序提示 。
|
让我们这样做,并遵循与之前相同的步骤:
-
点击
Ctrl+C
,停止应用程序。 -
创建本地可执行文件。
CLIquarkus 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/legumes.html
这一次,你可以看到我们的legumes列表。
响应式
你可以返回 响应式类型 来处理异步处理。Quarkus推荐使用 Mutiny 来编写响应式和异步代码。
Quarkus REST is naturally integrated with Mutiny.
你的节点可以返回 Uni
或 Multi
实例。
@GET
@Path("/{name}")
public Uni<Fruit> getOne(String name) {
return findByName(name);
}
@GET
public Multi<Fruit> getAll() {
return findAll();
}
当你有一个单一的结果时,使用 Uni
。当你有多个可能被异步发射的项目时,使用 Multi
。
您可以使用 Uni
和 Response
返回异步 HTTP 响应:Uni<Response>
。
有关 Mutiny 的更多详细信息,请参见外部参考:Mutiny - 一个直观的响应式编程库。