WebClient的retryWhen与onErrorResume是否互斥?重试未生效问题
问题说明
实现WebClient特定异常重试逻辑时,抛出PrematureCloseException后配置的重试逻辑未触发,需要确认retryWhen与onErrorResume是否互斥,以及重试失效的根因。
原有实现代码:
return client .sendWebhook(request, url) .exchangeToMono( response -> { final HttpStatus status = response.statusCode(); return response .bodyToMono(String.class) .defaultIfEmpty(StringUtils.EMPTY) .map( body -> { if (status.is2xxSuccessful()) { log.info("HTTP_SUCCESS[{}][{}] body[{}]", functionName, company, body); return ResponseEntity.ok().body(body); } else { log.warn( format( "HTTP_ERROR[%s][%s] status[%s] body[%s]", functionName, company, status, body)); return status.is4xxClientError() ? ResponseEntity.badRequest().body(body) : ResponseEntity.internalServerError().body(body); } }); }) .retryWhen( Retry.backoff(1, Duration.ofSeconds(1)) .filter( err -> { if (err instanceof PrematureCloseException) { log.warn("PrematureCloseException detected retrying."); return true; } return false; })) .onErrorResume( ex -> { log.warn( format( "HTTP_ERROR[%s][%s] errorInternal[%s]", functionName, company, ex.getMessage())); return Mono.just(ResponseEntity.internalServerError().body(ex.getMessage())); });
核心结论
retryWhen和onErrorResume不存在互斥关系,二者的执行逻辑完全由在响应式流中的位置决定:错误信号从上游向下游传播,先接触到错误的操作符先处理。- 你当前代码重试失效的核心原因是异常匹配逻辑存在缺陷,
retryWhen的过滤器没有识别到被上层包装的PrematureCloseException,因此没有触发重试。
原因详解
- 操作符执行逻辑
响应式流的信号传播规则是:订阅从下游向上游触发,错误/完成信号从上游向下游传递。你当前代码的操作符顺序本身是正确的:顺序为「请求发送+响应处理逻辑 -> retryWhen -> onErrorResume」,执行逻辑为:- 上游抛出错误后,会先传递给
retryWhen,符合重试条件则重新订阅上游逻辑发起重试 - 重试次数耗尽仍抛出错误时,错误才会传递给
onErrorResume,走兜底返回逻辑
如果顺序写反(onErrorResume放在retryWhen更靠近上游的位置),错误会先被onErrorResume捕获并返回正常的兜底响应,retryWhen接收不到错误信号,才会出现重试完全不触发的问题。
- 上游抛出错误后,会先传递给
- 异常匹配缺陷
Netty层抛出的PrematureCloseException在经过Spring WebClient、Reactor核心的传播过程中,大概率会被包装在WebClientRequestException、Exceptions.ReactiveException等外层异常中,你当前的过滤器仅判断顶层异常是否为PrematureCloseException,无法匹配到被包装的目标异常,因此过滤器始终返回false,不会触发重试,这也是你没有看到过滤器中打印的"PrematureCloseException detected retrying."日志的直接原因。
修复方案
调整retryWhen的异常过滤逻辑,递归遍历异常链匹配目标异常,修改后的过滤器代码如下:
.filter( err -> { Throwable current = err; // 递归遍历整个异常cause链 while (current != null) { if (current instanceof PrematureCloseException) { log.warn("PrematureCloseException detected retrying."); return true; } current = current.getCause(); } return false; })
额外注意事项:
- 如果重试的请求带有请求体,需要确保请求体是可重放的(比如字节数组、字符串等已经缓存的内容,而非一次性的数据流),否则重试时会出现请求体已消费的错误。
- 调试阶段可以在过滤器中打印异常的完整类型和cause链,确认实际抛出的异常结构,避免匹配遗漏。
内容的提问来源于stack exchange,提问作者Jonathan Chevalier
相关产品推荐
相关产品推荐

