1. 概述
在现代微服务架构中,服务之间经常需要通过 REST 接口通信来获取数据。
从 Spring 5 开始,我们可以使用 WebClient 来以响应式、非阻塞的方式执行 HTTP 请求。它是 WebFlux 框架的一部分,基于 Project Reactor 构建,提供了流畅的响应式 API,并在底层使用 HTTP 协议。
当我们发起一个请求时,返回的数据通常是 JSON 格式。而 WebClient 可以帮助我们将这些 JSON 数据转换为 Java 对象。
本文将介绍如何使用 WebClient 将 JSON 数组转换为 Java 中的 Object 数组、POJO 数组以及 POJO 列表。
2. 依赖配置
要在项目中使用 WebClient,我们需要在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
✅ 提示:spring-boot-starter-webflux
是核心依赖,包含了 WebClient 所需的一切。
3. 示例 JSON、POJO 和服务类
假设我们有一个接口地址为 http://localhost:8080/readers,它返回一个包含读者及其喜爱书籍信息的 JSON 数组:
[{
"id": 1,
"name": "reader1",
"favouriteBook": {
"author": "Milan Kundera",
"title": "The Unbearable Lightness of Being"
}
}]
为了处理这些数据,我们需要定义对应的 Reader
和 Book
类:
public class Reader {
private int id;
private String name;
private Book favouriteBook;
// getters and setters..
}
public class Book {
private final String author;
private final String title;
// getters and setters..
}
接下来我们编写一个服务实现类 ReaderConsumerServiceImpl
,其中注入了 WebClient
:
public class ReaderConsumerServiceImpl implements ReaderConsumerService {
private final WebClient webClient;
public ReaderConsumerServiceImpl(WebClient webclient) {
this.webclient = webclient;
}
// ...
}
⚠️ 注意:构造函数参数名拼写错误(应该是 webClient
),实际开发中要避免这种低级错误。
4. 映射 JSON 对象列表到 Java 集合
当从 REST 接口获取到 JSON 数组后,有多种方式可以将其转换为 Java 集合类型。下面我们来看几种常见做法,并提取出每个读者最喜爱的书籍集合。
4.1. Mono 与 Flux 的区别
Project Reactor 提供了两个主要的发布者类型:Mono 和 Flux。
- Flux
适用于处理 0 到 N 个元素,甚至可能是无限流(比如实时推送)。 - Mono
适用于只返回单个结果或没有结果的情况。
在本例中,我们知道返回的是一个完整的 JSON 数组,因此使用 Mono
4.2. 使用 Object 数组接收响应
首先我们使用 WebClient.get()
发起 GET 请求,并用 Mono<Object[]>
来接收响应体:
Mono<Object[]> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Object[].class).log();
然后通过 .block()
获取数组内容:
Object[] objects = response.block();
此时的 objects
实际上是由 Jackson 反序列化后的 LinkedHashMap
数组。我们需要借助 ObjectMapper
把它们转成 Reader
对象:
ObjectMapper mapper = new ObjectMapper();
最后提取每个读者的最爱书籍并收集到一个列表中:
return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, Reader.class))
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());
❌ 踩坑提醒:这种做法效率不高,因为中间多了一步 convertValue
的转换过程。
4.3. 使用 Reader 数组接收响应
更好的方式是直接告诉 Jackson 我们期望的目标类型为 Reader[]
:
Mono<Reader[]> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Reader[].class).log();
Reader[] readers = response.block();
return Arrays.stream(readers)
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());
✅ 优点:省去了 ObjectMapper.convertValue
的步骤,性能更好。
不过我们仍然需要手动将数组转为 List
,以便后续操作更方便。
4.4. 直接获取 List
如果希望 Jackson 直接返回一个 List<Reader>
,我们就不能简单地使用 List<Reader>.class
,因为泛型会在运行时被擦除。
正确的做法是使用 ParameterizedTypeReference
:
Mono<List<Reader>> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Reader>>() {});
List<Reader> readers = response.block();
return readers.stream()
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());
✅ 最佳实践:这种方式最直观也最高效,推荐优先使用。
🔍 为什么必须使用 ParameterizedTypeReference
?
由于 Java 泛型存在类型擦除的问题,如果我们直接传入 List<Reader>.class
,Jackson 无法知道泛型参数的具体类型。而通过匿名内部类创建的 ParameterizedTypeReference
,可以在运行时保留泛型信息,从而让 Jackson 正确反序列化。
5. 总结
在这篇文章中,我们探讨了使用 WebClient 处理 JSON 数组的三种不同方式:
- 使用
Object[]
接收并手动转换(不推荐) - 使用目标类型的数组如
Reader[]
(较优) - 使用
ParameterizedTypeReference
获取List<T>
(✅ 推荐)
通过合理使用泛型和 Jackson 的特性,我们可以写出更简洁高效的代码。
如需查看完整示例代码,请访问 GitHub 仓库。