
Spring Retry 支持通过 @Recover 方法为不同异常类型提供定制化恢复逻辑,结合 @Retryable 的精准异常过滤,可实现“仅对特定异常重试、对另一些异常直接返回默认值、对其他异常透传抛出”的灵活容错策略。
spring retry 支持通过 `@recover` 方法为不同异常类型提供定制化恢复逻辑,结合 `@retryable` 的精准异常过滤,可实现“仅对特定异常重试、对另一些异常直接返回默认值、对其他异常透传抛出”的灵活容错策略。
在实际微服务调用或外部依赖集成场景中,我们常需区分三类错误行为:
- ✅ 可重试的瞬时故障(如网络超时、限流拒绝)→ 应自动重试;
- ⚠️ 业务可兜底的非重试异常(如下游返回特定错误码)→ 不重试,但需返回预设默认值(如
7); - ❌ 不可恢复的致命异常(如参数校验失败、数据一致性破坏)→ 立即终止并原样抛出。
Spring Retry 原生不支持在 RetryListener.onError() 中修改返回值,也无法在 RetryPolicy 中将异常“转换”为成功结果——但这并不意味着无法实现题设中的 analyze_thrown_exception() 语义。关键在于:将异常分类逻辑前置到业务方法体内,再配合多态 @Recover 方法完成差异化恢复。
✅ 正确实践:异常分类 + 类型化恢复
首先,定义语义明确的自定义异常:
public class RetryableException extends RuntimeException {
public RetryableException(String message) { super(message); }
}
public class BusinessFallbackException extends RuntimeException {
public BusinessFallbackException(String message) { super(message); }
}然后,在 @Retryable 方法中主动捕获并分类异常,不依赖框架自动匹配,而是由你掌控何时抛出何种异常:
@Service
@Slf4j
public class MyRetryableService {
@Retryable(
value = {RetryableException.class}, // 仅对此类异常重试
maxAttempts = 3,
backoff = @Backoff(delay = 100, multiplier = 2)
)
public int do_something_that_may_throw_exception() {
try {
// 模拟真实调用
return performExternalCall();
} catch (Exception e) {
int analysis = analyze(e);
switch (analysis) {
case 1:
log.info("Business condition met → return default value 7 immediately");
return 7; // ✅ 不抛异常,直接返回成功结果
case 2:
log.warn("Transient failure → retry with RetryableException");
throw new RetryableException("Transient error: " + e.getMessage());
default:
log.error("Permanent failure → throw non-retryable exception");
throw new BusinessFallbackException("Permanent error: " + e.getMessage());
}
}
}
private int analyze(Exception e) {
// 实际中可基于异常类型、消息、HTTP 状态码等深度判断
if (e instanceof SocketTimeoutException || e.getMessage().contains("timeout")) {
return 2; // transient
} else if (e instanceof IllegalArgumentException && e.getMessage().contains("invalid id")) {
return 1; // fallback
}
return -1; // permanent
}
private int performExternalCall() {
// 模拟不稳定的外部调用
if (Math.random() < 0.7) throw new SocketTimeoutException("simulated timeout");
if (Math.random() < 0.3) throw new IllegalArgumentException("invalid id");
return (int) (Math.random() * 100);
}
// ? 当 RetryableException 耗尽重试次数后触发:返回默认值
@Recover
public int recover(RetryableException e) {
log.warn("All retries exhausted for transient error, returning fallback 7");
return 7;
}
// ? 对 BusinessFallbackException(非 @Retryable 列表内)直接触发 recover:
// 此处选择返回 7,也可 re-throw 或包装新异常
@Recover
public int recover(BusinessFallbackException e) {
log.info("Non-retryable business exception → return fallback 7");
return 7;
}
// ⚠️ 可选:兜底 recover,处理所有未显式声明的异常类型(避免 ExhaustedRetryException)
@Recover
public int recover(Exception e) {
log.error("Unexpected exception type, falling back to 7", e);
return 7;
}
}⚠️ 注意事项与最佳实践
-
@Recover方法签名必须匹配:参数列表需包含对应异常类型 + 原方法所有入参(若原方法有参数),返回类型必须与原方法一致; -
恢复方法优先级由异常继承关系决定:更具体的子类异常会优先匹配其
@Recover方法; -
不要在
@Retryable方法中吞掉所有异常:若统一catch(Exception)后无差别throw new RuntimeException(),则失去分类能力; -
启用 Spring Retry 功能需添加依赖与配置:
<!-- Maven --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>并在主类或配置类上添加
@EnableRetry; -
生产建议:将
analyze()逻辑封装为独立策略组件,支持 SPI 扩展,便于后续对接熔断、降级中心(如 Sentinel)。
通过这种“方法内异常路由 + 类型化恢复”模式,你完全可以在 Spring Retry 框架约束下,精准实现题设中 case 1/2/default 的三段式容错语义——既保持代码清晰性,又不牺牲框架的重试生命周期管理能力。

















