编写 JSON REST 服务
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 存储库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载存档。
解决方案位于 rest-json-quickstart
目录中。
创建 Maven 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数用双引号括起来,例如"-DprojectArtifactId=rest-json-quickstart"
此命令生成一个新项目,导入 Quarkus REST/Jakarta REST 和 Jackson 扩展,并特别添加以下依赖项
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jackson")
为了改善用户体验,Quarkus 注册了三个 Jackson Java 8 模块,因此您无需手动执行此操作。 |
Quarkus 还支持 JSON-B,因此,如果您喜欢 JSON-B 而不是 Jackson,您可以创建一个依赖于 Quarkus REST JSON-B 扩展的项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数用双引号括起来,例如"-DprojectArtifactId=rest-json-quickstart"
此命令生成一个新项目,导入 Quarkus REST/Jakarta REST 和 JSON-B 扩展,并特别添加以下依赖项
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jsonb</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jsonb")
如果您使用 JSON-B 和 JSON-P,请确保不要使用 目前,对这些方法的任何单个调用都将初始化一个新的 您可以将其作为静态导入来简化您的代码
|
有关 Quarkus REST 的更多信息,请参阅专用指南。 |
创建您的第一个 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 注释定义您的端点。
当安装 JSON 扩展时,例如 |
配置 JSON 支持
Jackson
在 Quarkus 中,通过 CDI 获取的默认 Jackson ObjectMapper
(并由 Quarkus 扩展使用)被配置为忽略未知属性(通过禁用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
功能)。
您可以通过在 application.properties
中设置 quarkus.jackson.fail-on-unknown-properties=true
或通过 @JsonIgnoreProperties(ignoreUnknown = false)
在每个类的基础上恢复 Jackson 的默认行为。
此外,ObjectMapper
被配置为以 ISO-8601 格式格式化日期和时间(通过禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
功能)。
可以通过在 application.properties
中设置 quarkus.jackson.write-dates-as-timestamps=true
来恢复 Jackson 的默认行为。如果您想更改单个字段的格式,可以使用 @JsonFormat
注释。
此外,Quarkus 使通过 CDI bean 配置各种 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。如果这样做,手动注入并应用所有 io.quarkus.jackson.ObjectMapperCustomizer
bean 在产生 ObjectMapper
的 CDI 生产者中非常重要。否则将阻止应用各种扩展提供的 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;
}
}
JSON-B
如上所述,Quarkus 提供了使用 JSON-B 而不是 Jackson 的选项,通过使用 quarkus-resteasy-jsonb
扩展。
按照上一节中描述的相同方法,可以使用 io.quarkus.jsonb.JsonbConfigCustomizer
bean 配置 JSON-B。
例如,如果需要使用 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
范围)。如果利用了后一种方法,则手动注入并应用所有 io.quarkus.jsonb.JsonbConfigCustomizer
bean 在产生 jakarta.json.bind.Jsonb
的 CDI 生产者中非常重要。否则将阻止应用各种扩展提供的 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;
}
}
创建一个前端
现在让我们添加一个简单的网页来与我们的 FruitResource
交互。Quarkus 会自动提供位于 META-INF/resources
目录下的静态资源。在 src/main/resources/META-INF/resources
目录中,添加一个 fruits.html
文件,其中包含此 fruits.html 文件中的内容。
您现在可以与您的 REST 服务交互
-
使用以下命令启动 Quarkus
CLIquarkus dev
Maven./mvnw quarkus:dev
Gradle./gradlew --console=plain quarkusDev
-
打开浏览器访问
https://:8080/fruits.html
-
通过表单将新水果添加到列表中
构建本机可执行文件
您可以使用常用命令构建本机可执行文件
quarkus build --native
./mvnw install -Dnative
./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 方法推断序列化类型时,Quarkus 会执行一些魔术。当您有以下 REST 方法时,Quarkus 确定 Fruit
将被序列化
@GET
public List<Fruit> list() {
// ...
}
Quarkus 通过在构建时分析 REST 方法自动执行此操作,这就是为什么在本指南的第一部分中我们不需要任何反射注册。
在 Jakarta REST 世界中,另一种常见的模式是使用 Response
对象。Response
带来了一些好处
-
您可以根据方法中发生的情况返回不同的实体类型(例如
Legume
或Error
); -
您可以设置
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
而不是豆类列表。
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();
}
}
现在让我们添加一个简单的网页来显示我们的豆类列表。在 src/main/resources/META-INF/resources
目录中,添加一个 legumes.html
文件,其中包含此 legumes.html 文件中的内容。
打开浏览器访问 https://:8080/legumes.html,您将看到我们的豆类列表。
有趣的部分从将应用程序作为本机可执行文件运行时开始
-
使用以下命令创建本机可执行文件
CLIquarkus 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 端点来确定 Legume
类是否需要一些反射。JSON 序列化库尝试获取 Legume
的字段列表,并获得一个空列表,因此它不会序列化字段的数据。
目前,当 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
执行它 -
打开浏览器并访问 https://:8080/legumes.html
这次,您可以看到我们的豆类列表。
变为响应式
您可以返回响应式类型来处理异步处理。Quarkus 建议使用 Mutiny 编写响应式和异步代码。
Quarkus REST 自然地与 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 - 一个直观的响应式编程库。