关于Spring的@Retryable(方法执行失败时自动重试)和@Recover(降级处理)
·
目录
一、@Retryable(方法执行失败时自动重试)
1. 基本概念
@Retryable 是 Spring Retry 模块提供的注解,用于在方法执行失败时自动重试。
2. 启用配置
@Configuration
@EnableRetry // 启用重试机制
public class AppConfig {
}
3. 核心参数详解
3.1 基础参数
@Retryable(
value = {SQLException.class, IOException.class}, // 触发重试的异常类型
maxAttempts = 3, // 最大尝试次数(包括第一次)
backoff = @Backoff(delay = 1000) // 退避策略
)
public void serviceMethod() {
// 业务逻辑
}
3.2 完整参数配置
@Retryable(
// 指定哪些异常触发重试
value = {RemoteAccessException.class, TimeoutException.class},
// 指定哪些异常不触发重试
exclude = {IllegalArgumentException.class},
// 最大尝试次数(包括第一次调用)
maxAttempts = 4,
// 最大重试次数(不包括第一次调用)
maxAttemptsExpression = "#{${retry.max.attempts}}",
// 退避策略
backoff = @Backoff(
delay = 1000, // 初始延迟时间(ms)
maxDelay = 10000, // 最大延迟时间(ms)
multiplier = 2, // 延迟倍数
random = true, // 是否使用随机延迟
delayExpression = "#{1000}" // 延迟表达式
),
// 重试监听器Bean名称
listeners = {"retryListener"}
)
public void processData(String data) {
// 业务逻辑
}
4. 退避策略(Backoff)
4.1 固定延迟
@Backoff(delay = 2000) // 每次重试固定等待2秒
4.2 指数退避
java
@Backoff(delay = 1000, multiplier = 2, maxDelay = 10000) // 重试间隔:1s, 2s, 4s, 8s, 10s, 10s...
4.3 随机退避
@Backoff(delay = 1000, maxDelay = 5000, random = true) // 重试间隔在1-5秒之间随机
5. 恢复机制(@Recover)
java
@Retryable(value = RemoteAccessException.class, maxAttempts = 3)
public String callExternalService() {
return externalService.call();
}
// 当所有重试都失败时执行
@Recover
public String recover(RemoteAccessException e) {
log.error("外部服务调用失败,执行降级逻辑", e);
return "default_value"; // 返回降级结果
}
注意:@Recover 方法必须:
-
与
@Retryable方法在同一个类中 -
第一个参数为异常类型
-
返回类型与
@Retryable方法相同 -
后续参数与
@Retryable方法参数一致
6. 复杂示例
6.1 条件重试
@Retryable(
value = Exception.class,
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2),
// 使用表达式控制重试条件
condition = "#{#root.args[0] != 'skipRetry'}" // 第一个参数不是'skipRetry'时才重试
)
public String processWithCondition(String mode, String data) {
return externalService.process(data);
}
6.2 动态配置
@Retryable(
maxAttemptsExpression = "#{@retryConfig.getMaxAttempts()}",
backoff = @Backoff(
delayExpression = "#{@retryConfig.getDelay()}",
multiplierExpression = "#{@retryConfig.getMultiplier()}"
)
)
public void dynamicRetryMethod() {
// 业务逻辑
}
@Component
public class RetryConfig {
public int getMaxAttempts() { return 3; }
public long getDelay() { return 1000L; }
public double getMultiplier() { return 2.0; }
}
7. 重试监听器
@Component
public class CustomRetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
log.info("重试开始");
return true; // 返回false可以终止重试
}
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
log.info("重试结束");
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
log.warn("第{}次重试失败", context.getRetryCount());
}
}
8. 最佳实践
8.1 幂等性处理
@Retryable(value = NetworkException.class, maxAttempts = 3)
@Transactional
public void updateOrderStatus(String orderId, String status) {
// 确保重试时操作是幂等的
Order order = orderRepository.findById(orderId);
if (!order.getStatus().equals(status)) {
order.setStatus(status);
orderRepository.save(order);
}
}
8.2 特定业务场景
@Service
public class PaymentService {
@Retryable(
value = {PaymentTimeoutException.class, NetworkException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 2000, multiplier = 1.5)
)
public PaymentResult processPayment(PaymentRequest request) {
// 支付处理逻辑
return paymentGateway.process(request);
}
@Recover
public PaymentResult paymentRecover(Exception e, PaymentRequest request) {
log.error("支付处理失败,订单号: {}", request.getOrderId(), e);
return PaymentResult.failed("支付处理超时,请稍后重试");
}
}
9. 注意事项
-
幂等性:确保重试的操作是幂等的
-
资源消耗:避免无限重试导致资源耗尽
-
超时设置:结合
@Timeout注解使用 -
异常处理:合理设置
value和exclude参数 -
性能影响:重试会增加响应时间,需合理配置延迟
10. Maven 依赖
xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.0</version>
</dependency>
这样配置后,Spring Retry 会在方法执行失败时自动按照配置进行重试,大大提高了系统的容错能力。
二、@Recover(降级)
1. 基本匹配规则
每个 @Recover 方法只匹配特定的 @Retryable 方法:
java
@Service
public class MyService {
@Retryable(value = RuntimeException.class, maxAttempts = 3)
public String methodA(String param) {
throw new RuntimeException("methodA失败");
}
@Retryable(value = IOException.class, maxAttempts = 2)
public String methodB(String param) {
throw new IOException("methodB失败");
}
// 只匹配 methodA 的恢复
@Recover
public String recoverA(RuntimeException e, String param) {
return "methodA的恢复结果: " + param;
}
// 只匹配 methodB 的恢复
@Recover
public String recoverB(IOException e, String param) {
return "methodB的恢复结果: " + param;
}
}
2. @Recover 方法的匹配条件
2.1 异常类型匹配
java
@Retryable(value = {ServiceException.class}, maxAttempts = 3)
public String processOrder(String orderId) {
throw new ServiceException("订单处理失败");
}
// 这个恢复方法会匹配
@Recover
public String orderRecover(ServiceException e, String orderId) {
return "订单处理降级";
}
// 这个不会匹配,因为异常类型不匹配
@Recover
public String otherRecover(RuntimeException e, String orderId) {
return "其他恢复";
}
2.2 返回类型匹配
java
@Retryable(value = Exception.class)
public String stringMethod() {
throw new RuntimeException("失败");
}
@Retryable(value = Exception.class)
public Integer integerMethod() {
throw new RuntimeException("失败");
}
// 匹配 stringMethod
@Recover
public String stringRecover(Exception e) {
return "字符串恢复";
}
// 匹配 integerMethod
@Recover
public Integer integerRecover(Exception e) {
return 0;
}
2.3 参数匹配
java
@Retryable(value = Exception.class)
public String methodWithParams(String name, int age) {
throw new RuntimeException("失败");
}
// 参数必须匹配:异常 + 原方法的所有参数
@Recover
public String paramRecover(Exception e, String name, int age) {
return "恢复: " + name + ", " + age;
}
3. 常见匹配问题
3.1 模糊匹配错误
java
@Retryable(value = IOException.class)
public String method1() { /* ... */ }
@Retryable(value = Exception.class)
public String method2() { /* ... */ }
// 模糊匹配:两个@Retryable方法都可能匹配这个恢复方法
@Recover
public String recover(Exception e) {
return "通用恢复";
}
3.2 解决方法:更具体的异常类型
java
@Recover
public String recoverIO(IOException e) {
return "IO异常恢复";
}
@Recover
public String recoverOther(Exception e) {
return "其他异常恢复";
}
4. 最佳实践示例
4.1 清晰的恢复方法命名
java
@Service
public class PaymentService {
@Retryable(value = PaymentException.class, maxAttempts = 3)
public PaymentResult processPayment(PaymentRequest request) {
// 支付逻辑
}
@Retryable(value = RefundException.class, maxAttempts = 2)
public RefundResult processRefund(RefundRequest request) {
// 退款逻辑
}
// 支付恢复方法
@Recover
public PaymentResult paymentRecover(PaymentException e, PaymentRequest request) {
log.warn("支付重试失败,执行降级: {}", request.getOrderId());
return PaymentResult.failed("支付系统繁忙");
}
// 退款恢复方法
@Recover
public RefundResult refundRecover(RefundException e, RefundRequest request) {
log.warn("退款重试失败: {}", request.getRefundId());
return RefundResult.failed("退款处理中,请稍后查询");
}
}
4.2 使用不同的返回类型
java
@Service
public class DataService {
@Retryable(value = DatabaseException.class)
public List<User> findUsers(String criteria) {
// 数据库查询
}
@Retryable(value = RemoteException.class)
public Map<String, Object> getRemoteData(String url) {
// 远程调用
}
@Recover
public List<User> recoverFindUsers(DatabaseException e, String criteria) {
return Collections.emptyList(); // 返回空列表
}
@Recover
public Map<String, Object> recoverRemoteData(RemoteException e, String url) {
return Map.of("error", "服务暂不可用"); // 返回错误Map
}
}
5. 验证匹配规则
java
@Test
public void testRecoverMatching() {
// methodA 失败 → 调用 recoverA
String resultA = myService.methodA("test");
// 输出: "methodA的恢复结果: test"
// methodB 失败 → 调用 recoverB
String resultB = myService.methodB("test");
// 输出: "methodB的恢复结果: test"
}
总结
-
不会所有
@Retryable方法都调用同一个@Recover方法 -
@Recover方法通过异常类型 + 返回类型 + 参数列表来精确匹配 -
每个
@Retryable方法应该有对应的专用@Recover方法 -
设计时要避免模糊匹配,使用具体的异常类型和返回类型
更多推荐


所有评论(0)