在什么情况下使用?
我使用的是 Spring Boot 2.x.x 版本的 Webflux。
在代码中使用 Webclient 时,我通常会在请求后使用 exchange()。原因是我所使用的 API 在失败时也会返回 200 HTTP Status 以及错误内容。
当时我犯了一个大错,以为 exchange() 和 retrieve() 是一样的。
如果你也这么想,那就错了。它们是不同的。那么 exchange 和 retrieve 到底有什么区别呢?
响应处理不同
根据 官方文档:
-
WebClient.ResponseSpec
retrieve()- 当响应状态为 200 OK 时返回响应主体。
- 默认情况下,4xx 和 5xx 响应会导致 WebClientResponseException 异常。要自定义错误处理,请使用 onStatus 处理程序。
-
reactor.core.publisher.Mono<ClientResponse>
exchange()- 自 Spring 5.3 起,因其可能导致内存泄漏和/或连接泄漏而被标记为废弃。
-
使用
exchange()时,应用程序有责任消费任何响应内容(无论场景是成功、错误、意外数据等)。不这样做会导致内存泄漏。 -
请改用
exchangeToMono(Function)或exchangeToFlux(Function)。
-
exchangeToMono, exchangeToFlux
- 自 Spring >= 5.3 起可用。
- 自 Spring Boot 版本 >= 2.4.x 起可用。
何时会发生内存泄漏
当你未在代码中使用 ClientResponse.bodyToMono 或 bodyToFlux 时。
.exchange()
.flatMap { clientResponse ->
if (clientResponse.statusCode() === HttpStatus.OK) {
// 请求成功
clientResponse.bodyToMono(CLASS::class.java)
}
else {
// HTTP 请求失败。此处发生了内存泄漏。
throw Exception()
}
}
在失败的情况下,你会面临内存泄漏,因为你没有消费响应主体。
如果不消费响应主体,连接就不会断开。
现在,会发生什么呢……你的 HTTP 连接池会被已使用的线程填满。当没有可用线程时,你的后续请求将会失败。
如何解决?
如果你使用的是 Spring Boot 2.4.x 以上版本,请按如下方式使用 exchangeToMono:
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else if (response.statusCode().is4xxClientError()) {
return response.bodyToMono(ErrorContainer.class);
}
else {
return Mono.error(response.createException());
}
});
如果你的 Spring Boot 版本低于 2.4.x,请使用 retrieve(),或者在抛出异常前手动消费响应主体。
示例如下:
clientResponse.bodyToMono<String>().defaultIfEmpty("").map {
throw Exception()
}
结论
我想,在正式使用库之前,我应该养成阅读并正确使用文档的习惯。
库并不能自动处理一切。
求知若饥,虚心若愚 (Stay Hunger, Stay Foolish)