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

重构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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:12:49