如何在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
相关产品推荐
相关产品推荐

