最新Spring Boot 2.5.0版本WebClient的awaitExchange使用异常问题
该异常是否为设计所致
是的,这是官方有意设计的行为。
Spring Framework 5.3(对应Spring Boot 2.5.0依赖的版本)废弃原来无lambda参数的awaitExchange()方法,本质是为了解决原方法容易出现连接资源泄漏的问题:旧方法返回的ClientResponse需要开发者手动保证资源释放,一旦漏处理就会导致连接池占满。
替换用的awaitExchange { }采用了作用域控制的资源管理逻辑:当传入的lambda执行完成后,框架会自动释放响应关联的所有资源,包含未消费的响应流。你如果直接把ClientResponse对象作为lambda的返回值带出作用域,后续调用awaitBody()时底层响应流已经被销毁,就会抛出你遇到的No value received via onNext for awaitSingle异常。
可行的替代方案
下面两个方案都可以满足你不在调用类中耦合awaitExchange和awaitBody逻辑的需求:
方案1:封装通用调用方法时传入响应解析逻辑
你可以将响应体处理逻辑作为函数参数传入封装了WebClient调用的工具类,不需要把ClientResponse透传到外层,示例代码如下:// 工具类中的通用调用方法 suspend fun <T> executeWebService( method: HttpMethod, uri: String, body: Any? = null, responseHandler: suspend (ClientResponse) -> T ): T { return webClient.method(method) .uri(uri) .apply { body?.let { bodyValue(it) } } .awaitExchange { responseHandler(it) } } // 业务调用处使用 val userInfo = executeWebService(HttpMethod.GET, "/user/1") { it.awaitBody<UserInfoDTO>() }这个方案的优势是资源由框架自动管理,不会出现泄漏问题,是官方推荐的实现方式。
方案2:绕过自动资源释放,手动控制响应生命周期
如果你确实需要将完整的ClientResponse对象返回给外层业务类处理,可以改用exchangeToMono配合awaitSingle的组合实现,示例代码如下:// 工具类中的调用方法 suspend fun executeWebService( method: HttpMethod, uri: String, body: Any? = null ): ClientResponse { return webClient.method(method) .uri(uri) .apply { body?.let { bodyValue(it) } } .exchangeToMono { Mono.just(it) } .awaitSingle() } // 业务调用处可单独提取响应体 val response = executeWebService(HttpMethod.GET, "/user/1") val userInfo = response.awaitBody<UserInfoDTO>()注意:该方案需要你手动保证响应资源被完全消费,如果你只需要读取响应头不需要读取响应体,请手动调用
response.releaseBody()释放连接,否则会出现连接池泄漏风险
内容的提问来源于stack exchange,提问作者Maxiepaxie

