使用带重试的同步WebClient时如何提取错误响应体?
解决WebClient同步调用中错误响应体捕获与重试问题
需求背景
需要将旧的RestTemplate调用替换为同步WebClient调用,满足以下要求:
- 同步调用外部接口
- 最多重试3次
- 4xx客户端错误不触发重试
- 重试耗尽或遇到4xx错误时,抛出包含状态码和响应体的自定义异常
核心问题
原实现中在onStatus回调里使用block()同步获取响应体,触发了Reactor线程阻塞错误:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-epoll-2
原因是WebClient基于Reactor实现,其IO线程不允许阻塞操作,必须使用异步链式处理。
解决方案
通过异步链式操作替代阻塞调用,调整异常构造逻辑,同时保留原有重试规则:
修正后的完整调用代码
String responseJson = WebClient.create() .method(httpMethod) .uri(url, uriBuilder -> uriBuilder.queryParams(queryParams).build()) .bodyValue(body) .retrieve() // 异步处理4xx错误:先获取响应体再抛出客户端异常 .onStatus(HttpStatusCode::is4xxClientError, response -> response.bodyToMono(String.class) .flatMap(responseBody -> Mono.error( new ApiInvokerClientErrorException(responseBody, response.statusCode()) )) ) // 异步处理其他非2xx错误:先获取响应体再抛出通用异常 .onStatus(status -> !status.is2xxSuccessful(), response -> response.bodyToMono(String.class) .flatMap(responseBody -> Mono.error( new ApiInvokerException(responseBody, response.statusCode()) )) ) .bodyToMono(String.class) .retryWhen(Retry.fixedDelay(3, Duration.ofMillis(delayBetweenRetriesInMillis)) // 过滤4xx异常,不触发重试 .filter(throwable -> !(throwable instanceof ApiInvokerClientErrorException)) // 重试耗尽时转换为统一自定义异常 .onRetryExhaustedThrow((spec, signal) -> { Throwable failure = signal.failure(); if (failure instanceof ApiInvokerException e) { return new UniformExceptionThatNeedToBeThrownOnError(e.getResponseBody(), e.getHttpStatusCode()); } else { return new RuntimeException( String.format("调用失败:%s %s,已重试3次", httpMethod.name(), url), failure ); } }) ) // 最后同步阻塞获取结果(仅在主线程执行,符合同步调用需求) .block();
异常类修正
修复原异常类中状态码赋值的错误,补充必要的getter方法:
public class ApiInvokerException extends Exception { private final String responseBody; private final HttpStatusCode httpStatusCode; public ApiInvokerException(String responseBody, HttpStatusCode statusCode) { super(String.format("接口调用错误,状态码:%d", statusCode.value())); this.responseBody = responseBody; this.httpStatusCode = statusCode; } public String getResponseBody() { return responseBody; } public HttpStatusCode getHttpStatusCode() { return httpStatusCode; } } public class ApiInvokerClientErrorException extends ApiInvokerException { public ApiInvokerClientErrorException(String responseBody, HttpStatusCode statusCode) { super(responseBody, statusCode); } }
关键说明
- 异步响应体处理:用
flatMap替代block(),在Reactor异步链中获取响应体后再构造异常,完全符合非阻塞模型,避免线程错误 - 重试规则保留:通过
filter排除ApiInvokerClientErrorException,确保4xx错误不会触发重试 - 异常统一转换:重试耗尽时,从
ApiInvokerException中提取响应体和状态码,构造业务需要的统一自定义异常抛出
内容的提问来源于stack exchange,提问作者Victoriia
相关产品推荐
相关产品推荐

