如何在Spring WebClient中捕获超时异常并调用指定方法?
解决方案:捕获WebClient超时异常并调用错误计数方法
你遇到的核心问题是:WebClient的响应式操作中,超时异常是在Reactive流内部异步抛出的,外层同步的try/catch无法捕获。要实现超时触发strategy.incrErrorCount(port)的需求,需要利用Reactive流的错误处理操作符来处理。
具体修改方案
通过doOnError操作符可以在异常发生时执行副作用逻辑(比如错误计数),同时不改变流的错误状态。调整后的代码如下:
private Mono<GameEntity> callApplication(GameEntity gameEntity) throws URISyntaxException { // 建议将WebClient实例化移到方法外作为类成员,避免重复创建实例 WebClient client = WebClient.create(); for(int i=0;i<NUM_OF_RETRY;++i) { String port = strategy.getNextInstancePort(); URI uri = new URI(String.format("http://localhost:%s/game/add", port)); return client.post() .uri(uri) .contentType(MediaType.APPLICATION_JSON) .body(Mono.just(gameEntity), GameEntity.class) .retrieve() .bodyToMono(GameEntity.class) .timeout(Duration.ofSeconds(3)) // 精准捕获超时异常,执行错误计数 .doOnError(TimeoutException.class, e -> { strategy.incrErrorCount(port); }) // 捕获WebClient请求异常,按需处理 .doOnError(WebClientRequestException.class, e -> { // 可添加请求异常的自定义处理逻辑 }) // 捕获其他未匹配的异常,执行错误计数 .doOnError(Exception.class, e -> { strategy.incrErrorCount(port); }); } // 重试耗尽后返回空Mono,符合Reactive规范(避免返回null) return Mono.empty(); }
关键说明
doOnError的作用:专门用于在流触发错误时执行副作用操作,不会中断或改变流的错误传播,异常会继续向下传递供上层处理。- 精准异常匹配:通过指定
TimeoutException.class,可以只在超时场景下触发错误计数,避免误处理其他类型的异常。 - Reactive规范优化:将原有的
return null改为return Mono.empty(),符合响应式流的设计原则,避免后续操作出现空指针问题。 - WebClient复用:把
WebClient.create()移到方法外部作为类成员变量,减少重复创建实例的性能开销。
如果想实现更优雅的响应式重试(替代原有的同步for循环),可以结合retryWhen操作符:
private Mono<GameEntity> callApplication(GameEntity gameEntity) { WebClient client = WebClient.create(); return Mono.defer(() -> { String port = strategy.getNextInstancePort(); URI uri; try { uri = new URI(String.format("http://localhost:%s/game/add", port)); } catch (URISyntaxException e) { return Mono.error(e); } return client.post() .uri(uri) .contentType(MediaType.APPLICATION_JSON) .body(Mono.just(gameEntity), GameEntity.class) .retrieve() .bodyToMono(GameEntity.class) .timeout(Duration.ofSeconds(3)) .doOnError(TimeoutException.class, e -> strategy.incrErrorCount(port)) .doOnError(WebClientRequestException.class, e -> strategy.incrErrorCount(port)); }) .retryWhen(Retry.fixedDelay(NUM_OF_RETRY, Duration.ofMillis(100))); }
这个版本用Mono.defer动态获取每次重试的目标端口,结合retryWhen实现响应式重试逻辑,更贴合Reactive编程的风格。
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

