51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

使用 WebClient 获取 JSON 对象集合

1、概览 {#1概览}

Spring 5 开始,可以使用 WebClient 以响应式、非阻塞的方式执行服务之间的 REST 通信。WebClient 是新的 WebFlux 框架的一部分,构建于 Project Reactor 之上。它使用 Fluent 风格的响应式 API,底层实现使用 HTTP 协议。

当发起 Web 请求时,数据通常会以 JSON 格式返回,本文将带你了解如何使用 WebClient 将响应的 JSON 数组转换为 Java Object 数组、POJO 数组和 POJO 集合。

2、依赖 {#2依赖}

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>

3、JSON、POJO 和 Service {#3jsonpojo-和-service}

从端点 http://localhost:8080/readers 开始,它以 JSON 数组的形式返回读者最喜欢的书籍列表:

[{
    "id": 1,
    "name": "reader1",
    "favouriteBook": { 
        "author": "Milan Kundera",
        "title": "The Unbearable Lightness of Being"
    }
}, {
    "id": 2,
    "name": "reader2"
    "favouriteBook": { 
        "author": "Douglas Adams",
        "title": "The Hitchhiker's Guide to the Galaxy"
    }
}]

还需要相应的 ReaderBook 类来处理数据:

public class Reader {
    private int id;
    private String name;
    private Book favouriteBook;

    // get 和 set 方法 省略
}
public class Book {
    private final String author;
    private final String title;

   // get 和 set 方法 省略
}

定义一个接口实现类 ReaderConsumerServiceImpl,并注入 WebClient 依赖:

public class ReaderConsumerServiceImpl implements ReaderConsumerService {

    private final WebClient webClient;

    public ReaderConsumerServiceImpl(WebClient webclient) {
        this.webclient = webclient;
    }

    // ...
}

4、映射 JSON 对象列表 {#4映射-json-对象列表}

当从 REST 请求中收到一个 JSON 数组时,有多种方法可将其转换为 Java Collection。

4.1、MonoFlux {#41mono-和-flux}

Project Reactor 引入了两种 Publisher 实现: MonoFlux

当需要处理零到多或可能无限的结果时,Flux<T> 就能派上用场,例如 Twitter feed。

当知道结果会一次性返回时(就像本例一样),可以使用 Mono<T>

4.2、Object 数组 {#42object-数组}

使用 WebClient.get 进行 GET 调用,并使用 Object[] 类型的 Mono 来收集响应:

Mono<Object[]> response = webClient.get()
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .bodyToMono(Object[].class).log();

接着,将 Body 提取到 Object 数组中:

Object[] objects = response.block();

这里的际 Object 实上是一个包含数据的任意结构。现在把它转换成一个 Reader 对象数组。

为此,需要一个 ObjectMapper

ObjectMapper mapper = new ObjectMapper();

如上,以内联的方式声明了它。但这通常是定义在类私有成员变量中的。

最后,提取读者最喜欢的书籍,并将它们收集到一个 List 中:

return Arrays.stream(objects)
  .map(object -> mapper.convertValue(object, Reader.class))
  .map(Reader::getFavouriteBook)
  .collect(Collectors.toList());

当指定 Jackson Deserializer 生成的目标类型为 Object 时,它实际上会将 JSON 反序列化为一系列 LinkedHashMap 对象。使用 convertValue 进行后处理的效率很低。如果在反序列化过程中向 Jackson 提供所需的类型,就可以避免这种情况。

4.3、Reader 数组 {#43reader-数组}

可以向 WebClient 指定 Reader[] 代替 Object[]

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。但是,仍然需要进行额外的转换,以便使用 Java Stream API,并使代码能与 List 配合使用。

4.4、Reader List {#44reader-list}

如果想让 Jackson 生成一个 List<Reader> 而不是数组,可以向该方法提供了一个匿名内部类形式的 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

当类型信息在运行时可用时,Spring WebClient 可以轻松地将 JSON 反序列化为 Reader.class

然而,对于泛型来说,如果尝试使用 List<Reader>.class,会发生类型擦除。因此,Jackson 将无法确定泛型的类型参数。

通过使用 ParameterizedTypeReference,可以解决这个问题。将其实例化为匿名内部类利用了泛型类的子类包含编译时类型信息的特性,这些信息不受类型擦除的影响,并且可以通过反射来使用。

5、总结 {#5总结}

本文介绍了如何使用 WebClient 将响应的 JSON 数组转换为 Java Object 数组、POJO 数组以及 List<T>(使用 ParameterizedTypeReference 提供泛型信息)。


Ref:https://www.baeldung.com/spring-webclient-json-list

赞(2)
未经允许不得转载:工具盒子 » 使用 WebClient 获取 JSON 对象集合