
Spring Retry 中 @Retryable(exclude = XException.class) 并非“仅对指定异常重试”,而是“对除该异常外的所有异常重试”;若未显式设置 include/retryFor,且 exclude 为空,则默认重试所有异常——因此需明确使用 retryFor + noRetryFor 组合来精准控制重试策略。
spring retry 中 `@retryable(exclude = xexception.class)` 并非“仅对指定异常重试”,而是“对除该异常外的所有异常重试”;若未显式设置 `include`/`retryfor`,且 `exclude` 为空,则默认重试所有异常——因此需明确使用 `retryfor` + `noretryfor` 组合来精准控制重试策略。
在 Spring Retry 2.0+(对应 Spring Boot 2.4+ 及较新 Spring Integration 版本)中,@Retryable 注解的语义已发生重要演进:include 和 exclude 属性已被标记为 @Deprecated,取而代之的是语义更清晰、行为更确定的 retryFor 和 noRetryFor。
❗核心误区澄清
-
exclude = MessageTimeoutException.class不表示“遇到此异常才重试”,而是表示 “不对此异常重试,但对其它所有异常都重试”。 - 若未设置
include(或retryFor),且exclude为空,则 Spring Retry 默认对 所有抛出的异常均执行重试 —— 这正是你观察到MessageTimeoutException仍被重试的根本原因:exclude未生效,因为注解实际未匹配到该异常类型(类路径不一致或继承关系误判)。
✅ 正确做法:优先使用 retryFor + noRetryFor
根据你的需求——“仅在非 MessageTimeoutException 的异常下重试”,应显式声明可重试的异常范围,并排除特定类型:
@Override
@Retryable(
retryFor = Exception.class, // 显式声明:所有 Exception 及其子类都可重试(兜底)
noRetryFor = org.springframework.integration.MessageTimeoutException.class // 精确排除
)
public byte[] sendMessage(byte[] message, String guid) {
responseBytes = tcpClientGateway.send(message);
return responseBytes;
}⚠️ 注意包路径:日志中明确显示异常全类名为
org.springframework.integration.MessageTimeoutException,务必使用完整限定名,避免因自定义同名类或导入错误导致匹配失败。
? 验证 noRetryFor 是否生效的关键点
-
类加载一致性:确保
@Retryable所在类与MessageTimeoutException处于同一类加载器上下文,且无重复引入不同版本的spring-integration-ip。 -
异常抛出层级:
@Retryable作用于方法级,仅捕获该方法直接抛出的异常。若异常被上层(如TcpOutboundGateway或RequestHandlerRetryAdvice)提前拦截并包装(例如转为MessagingException),原始MessageTimeoutException将不可见,导致noRetryFor失效。此时需检查调用链中是否存在异常转换逻辑。 -
Advice 与 Annotation 共存风险:你同时配置了
@Retryable注解和RequestHandlerRetryAdvice(基于 Spring Integration 的 AOP Advice)。二者可能叠加或冲突。推荐二选一:- ✅ 纯注解方案:移除
retryAdvice()和adviceChain,依赖@EnableRetry+@Retryable自动代理; - ✅ 纯 Advice 方案:删除
@Retryable,完全由RequestHandlerRetryAdvice控制,并在RetryTemplate中配置SimpleRetryPolicy的setExceptionsToIgnore(...):
- ✅ 纯注解方案:移除
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryPolicy.setExceptionsToIgnore(new Class[]{org.springframework.integration.MessageTimeoutException.class});? 最佳实践总结
| 场景 | 推荐配置 |
|---|---|
| Spring Boot ≥ 2.4 + Spring Retry ≥ 2.0 | 使用 @Retryable(retryFor = ..., noRetryFor = ...),弃用 include/exclude
|
| 需要细粒度控制(如忽略多个异常) | noRetryFor = {AException.class, BException.class} |
| 与 Spring Integration Gateway 深度集成 | 优先采用 RequestHandlerRetryAdvice + RetryTemplate.setExceptionsToIgnore(),避免注解与 Advice 冲突 |
| 调试重试行为 | 启用 DEBUG 日志:logging.level.org.springframework.retry=DEBUG,观察 RetryTemplate 是否匹配到目标异常 |
通过明确声明 retryFor 并精准指定 noRetryFor 类型,即可严格实现“仅对非超时异常重试”的业务诉求,避免因语义误解导致的重试逻辑失效。

















