You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用带重试的同步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);
    }
}

关键说明

  1. 异步响应体处理:用flatMap替代block(),在Reactor异步链中获取响应体后再构造异常,完全符合非阻塞模型,避免线程错误
  2. 重试规则保留:通过filter排除ApiInvokerClientErrorException,确保4xx错误不会触发重试
  3. 异常统一转换:重试耗尽时,从ApiInvokerException中提取响应体和状态码,构造业务需要的统一自定义异常抛出

内容的提问来源于stack exchange,提问作者Victoriia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:04:50