
Spring Retry 的 @Retryable(exclude = ...) 并非“仅对指定异常不重试”,而是“在默认重试所有异常的前提下,排除这些异常”;若未显式配置 include/retryFor,exclude 单独使用可能失效,需改用 retryFor + noRetryFor 组合确保语义清晰、行为可靠。
spring retry 的 `@retryable(exclude = ...)` 并非“仅对指定异常不重试”,而是“在默认重试所有异常的前提下,排除这些异常”;若未显式配置 `include`/`retryfor`,`exclude` 单独使用可能失效,需改用 `retryfor` + `noretryfor` 组合确保语义清晰、行为可靠。
在 Spring Retry 2.0+(对应 Spring Boot 2.4+)中,@Retryable 的属性设计已明确语义化:retryFor 定义“哪些异常必须重试”,noRetryFor 明确“哪些异常绝对不重试”。而旧版的 include 和 exclude 已被标记为 @Deprecated,其逻辑易引发误解——尤其当 include 为空时,exclude 的行为依赖隐式默认策略(即“所有异常都重试,除非被排除”),但在混合使用 AOP 代理(如 Spring Integration 的 RequestHandlerRetryAdvice)和注解驱动重试时,二者可能冲突,导致 exclude 实际未生效。
以您遇到的问题为例:MessageTimeoutException 仍被重试,根本原因在于:
- 您仅设置了
@Retryable(exclude = MessageTimeoutException.class); - 但当前 Spring Retry 版本(从堆栈可见为
spring-retry-1.3.1,属较老版本)或与 Spring Integration 的RequestHandlerRetryAdvice协同时,注解配置未被RetryTemplate的底层策略识别; - 更关键的是:
exclude仅在include显式声明的前提下才安全生效;若include为空(默认),部分场景下框架可能忽略exclude,退回到“全部重试”。
✅ 正确做法是统一使用语义明确的新属性,并确保与 RetryTemplate 策略一致:
@Override
@Retryable(
retryFor = Exception.class, // 显式声明:所有 Exception 都应重试(覆盖默认行为)
noRetryFor = MessageTimeoutException.class // 明确排除:此异常绝不重试
)
public byte[] sendMessage(byte[] message, String guid) {
responseBytes = tcpClientGateway.send(message);
return responseBytes;
}
? 注意:
retryFor = Exception.class是关键。它显式激活重试规则,使noRetryFor生效;若省略,即使noRetryFor存在,某些旧版本也可能降级为“无重试策略”。
同时,请检查 RetryTemplate 的策略是否与注解协同工作。您当前代码中同时存在:
- 注解式
@Retryable(由AnnotationAwareRetryOperationsInterceptor处理); - 手动配置的
RequestHandlerRetryAdvice(属于 Spring Integration 框架级重试)。
⚠️ 二者不可混用!否则会导致重复重试或策略冲突。推荐方案是 二选一:
-
✅ 推荐:纯注解驱动(简洁可控)
移除RequestHandlerRetryAdvice,启用@EnableRetry,并确保目标方法被 Spring 代理(如sendMessage在@ServiceBean 中且调用来自外部 Bean):@Configuration @EnableRetry public class RetryConfig { @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); template.setRetryPolicy(new SimpleRetryPolicy(3)); template.setBackOffPolicy(new FixedBackOffPolicy() {{ setBackOffPeriod(50); }}); return template; } } ❌ 避免:注解 +
RequestHandlerRetryAdvice双重配置
您当前emirOutbound中已注入retryAdvice(),此时@Retryable注解将被忽略——因为 Spring Integration 的RequestHandlerRetryAdvice会接管重试逻辑,完全绕过 Spring Retry 注解处理器。
? 验证技巧:
启动时添加日志观察重试器初始化:
logging:
level:
org.springframework.retry: DEBUG
org.springframework.integration.handler.advice: DEBUG
若看到 RetryOperationsInterceptor 日志,则注解生效;若只有 RequestHandlerRetryAdvice 日志,则注解未被触发。
? 总结:
-
exclude≠ “仅排除某异常”,而是“在 include 基础上排除”,语义模糊且已弃用; - 务必使用
retryFor+noRetryFor组合,显式定义重试边界; - 在 Spring Integration 场景中,优先选择
RequestHandlerRetryAdvice(统一管控)或纯@Retryable(轻量灵活),避免混合; - 确保异常类型完全匹配(如
org.springframework.integration.MessageTimeoutException,注意包路径)。
遵循以上原则,即可精准控制 MessageTimeoutException 不重试,其余异常按策略重试。










