Spring @Retryable 注解未生效,方法仅执行一次即抛出异常,根本原因是项目中引入了多个版本的 spring-retry(如 1.1.2.RELEASE 和 1.2.4.RELEASE),引发类加载冲突与 AOP 代理失效。
spring `@retryable` 注解未生效,方法仅执行一次即抛出异常,根本原因是项目中引入了多个版本的 `spring-retry`(如 1.1.2.release 和 1.2.4.release),引发类加载冲突与 aop 代理失效。
在 Spring 生态中,@Retryable 的底层依赖于 spring-retry 模块提供的 AnnotationAwareRetryOperationsInterceptor——该拦截器由 Spring AOP 动态织入目标方法调用链。但当项目中存在多个不同版本的 spring-retry 依赖(例如通过不同间接依赖引入 1.1.2.RELEASE 和 1.2.4.RELEASE),会导致以下关键问题:
- 类路径下出现重复的 RetryOperationsInterceptor、RetryAnnotationAdvisor 等核心类;
- Spring 容器无法正确识别或注册唯一的 RetryOperationsInterceptor Bean;
- @EnableRetry 启用的切面代理被破坏,AnnotationAwareRetryOperationsInterceptor.invoke() 虽出现在堆栈中(说明 AOP 链路曾被触发),但因版本不一致导致重试逻辑实际未执行(如 RetryTemplate 初始化失败、RetryPolicy 未正确绑定等);
- 最终表现为:异常抛出后无重试行为,日志仅记录一次,与预期 maxAttempts=3 完全不符。
✅ 验证与修复步骤
-
检查实际依赖树
运行以下命令定位冲突版本:./mvnw dependency:tree | grep "spring-retry" # 或 Gradle ./gradlew dependencies | grep "spring-retry"
输出中若出现类似:
+- org.springframework.retry:spring-retry:jar:1.1.2.RELEASE:compile \- com.some-lib:some-starter:jar:2.0.0:compile \- org.springframework.retry:spring-retry:jar:1.2.4.RELEASE:compile
即确认存在多版本共存。
-
统一并锁定版本
在 pom.xml 中显式声明唯一权威版本(推荐使用较新稳定版,如 1.3.4 或 1.4.2),并通过排除传递依赖中的旧版本: <dependency><groupid>org.springframework.retry</groupid><artifactid>spring-retry</artifactid><version>1.4.2.RELEASE</version></dependency><!-- 若某依赖强制引入旧版,需排除 --><dependency><groupid>com.example</groupid><artifactid>legacy-starter</artifactid><exclusions><exclusion><groupid>org.springframework.retry</groupid><artifactid>spring-retry</artifactid></exclusion></exclusions></dependency>
-
确保基础配置完整
- 主启动类或配置类添加 @EnableRetry;
- 目标方法必须是 public,且不能在同一个类内自调用(否则绕过代理,重试失效);
- 方法所在 Bean 需由 Spring 容器管理(非 new 实例化);
- 避免与 @Transactional 组合时的代理顺序陷阱(建议将 @Retryable 放在事务边界外,或使用 @EnableRetry(proxyTargetClass = true) 强制 CGLIB 代理)。
⚠️ 特别注意:@Transactional + @Retryable 的协作风险
如问题中所示,重试方法内部嵌套在 @Transactional 中,可能导致事务传播行为干扰重试逻辑(例如:第一次失败后事务已回滚,重试时仍处于同一事务上下文)。最佳实践是将 @Retryable 应用于独立的服务方法,并由调用方控制事务边界,例如:
@Service
public class PaymentRetryService {
@Retryable(
value = {ClientTransportException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 5000, multiplier = 2)
)
public void createQrPayment(Long paymentId, Map<string string> params) {
// 调用外部 WS,不包含 @Transactional
bankClient.createQrPayment(paymentId, params);
}
@Recover
public void recover(ClientTransportException e, Long paymentId) {
log.error("All retry attempts failed for payment {}", paymentId, e);
// 触发补偿逻辑(如落库待人工处理)
}
}
// 调用方负责事务
@Service
@Transactional
public class PaymentServiceImpl {
public ConfirmResult confirmOrder(long paymentId, String code) {
// ... 业务逻辑
try {
paymentRetryService.createQrPayment(paymentId, params); // 重试在此发生
} catch (Exception ex) {
// 重试全部失败后的兜底处理
}
}
}</string>
? 总结
@Retryable 失效的常见原因中,依赖版本冲突是最隐蔽却高频的问题。它不会直接报错,而是静默破坏 AOP 代理链。务必通过 dependency:tree 严格校验 spring-retry 的唯一性;同时遵循“单一职责+明确事务边界”的设计原则,才能让重试机制稳定、可预期地工作。










