重构WebClient非响应式调用方法:移除循环与休眠,基于Mono重复机制
重构WebClient重试逻辑的可行方案
以下是几种可行的重构方案,均能移除手动for循环+线程休眠的重试逻辑,利用WebClient的响应式特性实现需求,同时保持方法为非响应式:
方案一:使用Spring WebFlux内置Retry API(推荐)
利用WebClient的响应式管道结合retryWhen操作符,通过Retry类精准配置重试规则(次数、间隔、触发条件),逻辑更贴合响应式编程范式。
import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; import com.fasterxml.jackson.databind.JsonNode; public class YourService { private static final int NUMBER_OF_ATTEMPTS = 3; private static final long RETRY_DELAY_MS = 1000; private final WebClient webClient; // 构造函数注入WebClient public YourService(WebClient webClient) { this.webClient = webClient; } public String getSomething() { return webClient.get() .retrieve() .bodyToMono(JsonNode.class) // 检查响应有效性,无效则抛出特定异常触发重试 .flatMap(response -> { if (response.get("something") != null) { return Mono.just(response.get("something").asText()); } else { return Mono.error(new IllegalStateException("Missing 'something' field")); } }) // 配置重试规则:最多重试NUMBER_OF_ATTEMPTS-1次(含首次请求共NUMBER_OF_ATTEMPTS次),固定间隔1秒 .retryWhen(Retry.fixedDelay(NUMBER_OF_ATTEMPTS - 1, RETRY_DELAY_MS) .filter(throwable -> throwable instanceof IllegalStateException)) // 同步阻塞获取结果,重试耗尽时抛出原方法指定异常 .blockOptional() .orElseThrow(() -> new RuntimeException("Number of calls to external api is exceeded")); } }
关键说明:
- 仅对
IllegalStateException触发重试,避免网络错误等无关异常被无意义重试 Retry.fixedDelay的参数是重试次数,而非总请求次数,因此用NUMBER_OF_ATTEMPTS-1- 保持和原方法一致的异常类型与抛出时机
方案二:使用Spring Retry注解简化逻辑
若项目已引入Spring Retry依赖,可通过注解快速实现重试,无需手动编写响应式重试逻辑。
第一步:引入依赖(Maven示例)
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
第二步:实现代码
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Recover; import org.springframework.retry.annotation.Retryable; import org.springframework.web.reactive.function.client.WebClient; import com.fasterxml.jackson.databind.JsonNode; public class YourService { private static final int NUMBER_OF_ATTEMPTS = 3; private final WebClient webClient; public YourService(WebClient webClient) { this.webClient = webClient; } @Retryable( value = {IllegalStateException.class}, maxAttempts = NUMBER_OF_ATTEMPTS, backoff = @Backoff(delay = 1000) ) public String getSomething() { JsonNode response = webClient.get() .retrieve() .bodyToMono(JsonNode.class) .block(); if (response.get("something") != null) { return response.get("something").asText(); } else { throw new IllegalStateException("Missing 'something' field"); } } @Recover public String recover(IllegalStateException e) { throw new RuntimeException("Number of calls to external api is exceeded"); } }
关键说明:
- 在Spring配置类上添加
@EnableRetry注解启用重试功能 @Retryable指定触发重试的异常、最大尝试次数和退避间隔@Recover方法在重试耗尽后执行,抛出原方法要求的异常
方案三:自定义响应式重试逻辑(灵活控制)
若需要更灵活的重试规则(如动态调整间隔、自定义终止条件),可使用repeatWhen操作符结合Flux逻辑实现:
import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import reactor.core.publisher.Flux; import com.fasterxml.jackson.databind.JsonNode; public class YourService { private static final int NUMBER_OF_ATTEMPTS = 3; private static final long RETRY_DELAY_MS = 1000; private final WebClient webClient; public YourService(WebClient webClient) { this.webClient = webClient; } public String getSomething() { return Mono.defer(() -> webClient.get() .retrieve() .bodyToMono(JsonNode.class)) // 过滤仅保留包含有效字段的响应 .filter(response -> response.get("something") != null) .map(response -> response.get("something").asText()) // 控制重试次数与间隔 .repeatWhen(completed -> completed .zipWith(Flux.range(1, NUMBER_OF_ATTEMPTS - 1)) .flatMap(tuple -> Mono.delay(RETRY_DELAY_MS))) // 获取第一个有效结果,重试耗尽时抛出异常 .blockFirst() .orElseThrow(() -> new RuntimeException("Number of calls to external api is exceeded")); } }
关键说明:
Mono.defer确保每次重试都创建新的请求实例,避免请求被缓存repeatWhen结合zipWith精准控制重试次数(最多NUMBER_OF_ATTEMPTS-1次)- 逻辑简洁,适合需要自定义重试触发逻辑的场景
内容的提问来源于stack exchange,提问作者Tchorzyksen
相关产品推荐
相关产品推荐

