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

Spring WebClient如何在doOnError抛出异常时终止重试流程

问题根源

现有代码的核心问题有两个:

  1. 请求头在Mono初始化时就固定读取了headerValue的初始值,重试时不会重新读取更新后的缓存值,就算你在doOnError里更新了AtomicReference,重试的请求还是携带旧的授权信息。
  2. doOnError是副作用操作,仅消费异常不会改变下游的异常传递逻辑。tryToLoginAgain抛出的异常会被后续的重试逻辑吞没,你配置的默认重试策略会对所有异常触发重试,所以会无限循环重试。

解决方案

按以下步骤修改即可实现需求:

1. 自定义不可重试异常

先定义一个登录失败的终止异常,用于区分需要重试和需要直接终止的场景:

// 自定义异常,标识登录彻底失败,不需要重试
public class HttpClientLoginFatalException extends RuntimeException {
    public HttpClientLoginFatalException(String message, Throwable cause) {
        super(message, cause);
    }
}

2. 修改重试前的登录逻辑

把tryToLoginAgain改成有返回值的方法,登录失败时抛出上面定义的终止异常:

private Mono<T> tryToLoginAgain(AtomicReference<String> headerValue, HttpClientLoginException e) {
    try {
        loginService.removeAccessTokenFromCache();
        headerValue.set(loginService.getAuthorizationHeaderValue());
        // 登录成功,返回原异常触发重试
        return Mono.error(e);
    } catch (Exception loginEx) {
        // 登录失败,返回不可重试的终止异常
        return Mono.error(new HttpClientLoginFatalException("重新登录失败,终止请求", loginEx));
    }
}

3. 改造请求构造逻辑

用Mono.defer包裹请求构造逻辑,保证每次重试都会重新读取最新的授权头,同时修改重试策略过滤不可重试异常:

public <T> T myMethod(...) {
    try {
        AtomicReference<String> headerValue = new AtomicReference<>(loginService.getAuthorizationHeaderValue());

        Mono<T> monoResult = Mono.defer(() -> 
            // 放在defer里,每次重试都会重新构建请求,读取最新的headerValue
            webclient.get()
                .uri(uri)
                .accept(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, headerValue.get())
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> throwHttpClientLoginException())
                .bodyToMono(type)
            )
            .onErrorResume(HttpClientLoginException.class, e -> tryToLoginAgain(headerValue, e))
            .retryWhen(Retry.backoff(MAX_NUMBER_RETRIES, Duration.ofSeconds(5))
                // 只有HttpClientLoginException才触发重试,遇到登录失败的终止异常直接停止
                .filter(throwable -> throwable instanceof HttpClientLoginException)
            );

        result = monoResult.block();
    } catch(Exception e) {
        throw new HttpClientException("There was an error while sending the request");
    }
    return result;
}

逻辑说明

  • 首次请求如果返回4xx,会触发HttpClientLoginException,进入onErrorResume执行重新登录逻辑
  • 重新登录成功的话返回原异常,触发重试,defer会重新构造请求携带新的授权头发起请求
  • 重新登录失败的话返回HttpClientLoginFatalException,重试策略过滤到该异常不会触发重试,异常会直接抛出被外层catch捕获,终止整个请求流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 20:15:08