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"
}
}]
还需要相应的 Reader
和 Book
类来处理数据:
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、Mono
和 Flux
{#41mono-和-flux}
Project Reactor 引入了两种 Publisher
实现: Mono
和 Flux
。
当需要处理零到多或可能无限的结果时,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