Spring WebClient如何在doOnError抛出异常时终止重试流程
问题根源
现有代码的核心问题有两个:
- 请求头在Mono初始化时就固定读取了
headerValue的初始值,重试时不会重新读取更新后的缓存值,就算你在doOnError里更新了AtomicReference,重试的请求还是携带旧的授权信息。 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
相关产品推荐
相关产品推荐

