Webflux 中 exchange 和 retrieve 的区别

在什么情况下使用?

我使用的是 Spring Boot 2.x.x 版本的 Webflux。

在代码中使用 Webclient 时,我通常会在请求后使用 exchange()。原因是我所使用的 API 在失败时也会返回 200 HTTP Status 以及错误内容。

当时我犯了一个大错,以为 exchange()retrieve() 是一样的。

如果你也这么想,那就错了。它们是不同的。那么 exchangeretrieve 到底有什么区别呢?

响应处理不同

根据 官方文档

  • 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.bodyToMonobodyToFlux 时。


.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)

参考资料

AD