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

如何在Spring @Retryable的delayExpression中引用抛出的异常?

基于@Retryable实现动态重试延迟(从异常中提取Retry-After值)

你的需求完全可以实现,不需要改用RetryTemplate,只需通过自定义BackoffPolicy扩展Spring Retry的重试逻辑即可。

核心问题说明

@Backoff的delayExpression默认上下文仅包含方法调用参数(通过#root.args访问),确实无法直接获取重试过程中抛出的异常。但Spring Retry允许通过自定义BackoffPolicy来访问RetryContext,而RetryContext中保存了上一次重试抛出的异常实例。

实现步骤

1. 自定义动态退避策略

创建一个实现BackoffPolicy的类,从RetryContext中提取异常里的retryAfter值作为重试延迟:

import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackoffPolicy;
import org.springframework.retry.backoff.Sleeper;
import org.springframework.retry.backoff.ThreadWaitSleeper;

public class DynamicRetryAfterBackoffPolicy implements BackoffPolicy {

    private final Sleeper sleeper = new ThreadWaitSleeper();

    @Override
    public void backOff(RetryContext context) throws BackOffInterruptedException {
        Throwable lastException = context.getLastThrowable();
        if (lastException instanceof RateLimitExceededException rateLimitEx) {
            // 从异常中获取动态重试延迟时间
            long retryDelay = rateLimitEx.getRetryAfter();
            try {
                sleeper.sleep(retryDelay);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new BackOffInterruptedException("重试延迟线程被中断", e);
            }
        } else {
            // 非限流异常的默认退避(可根据需求调整)
            try {
                sleeper.sleep(0);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new BackOffInterruptedException("重试延迟线程被中断", e);
            }
        }
    }

    @Override
    public BackoffPolicy clone() {
        try {
            return (BackoffPolicy) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new IllegalStateException("无法克隆退避策略", e);
        }
    }
}

2. 修改@Retryable注解配置

在@Retryable中指定自定义的退避策略,替代原有的delayExpression配置:

@Component
public class MyService {

    @Retryable(
            retryFor = {RateLimitExceededException.class},
            backoff = @Backoff(policy = DynamicRetryAfterBackoffPolicy.class)
    )
    public void doSomeApiCall(String parameters) {
        // 调用第三方API,解析429响应的Retry-After头
        long retryAfter = 30000; // 实际为从响应头动态获取的值
        throw new RateLimitExceededException(retryAfter);
    }

    public static class RateLimitExceededException extends RuntimeException {
        private final long retryAfter;

        public RateLimitExceededException(long retryAfter) {
            this.retryAfter = retryAfter;
        }

        public long getRetryAfter() {
            return retryAfter;
        }
    }
}

额外优化建议

  • 可以给自定义退避策略添加配置属性(比如默认延迟、最大延迟),通过@Backoff的delay、maxDelay等参数传入,增强灵活性
  • 若需要记录重试延迟日志,可在backOff方法中添加日志输出,便于排查问题

内容的提问来源于stack exchange,提问作者Becca Gaspard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:30:19