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"
    }
}]

为了处理这些数据,我们需要定义对应的 ReaderBook 类:

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. MonoFlux 的区别

Project Reactor 提供了两个主要的发布者类型:MonoFlux

  • 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 仓库


原始标题:Get List of JSON Objects with WebClient