目录

一、@Retryable(方法执行失败时自动重试)

1. 基本概念

2. 启用配置

3. 核心参数详解

3.1 基础参数

3.2 完整参数配置

4. 退避策略(Backoff)

4.1 固定延迟

4.2 指数退避

4.3 随机退避

5. 恢复机制(@Recover)

6. 复杂示例

6.1 条件重试

6.2 动态配置

7. 重试监听器

8. 最佳实践

8.1 幂等性处理

8.2 特定业务场景

9. 注意事项

10. Maven 依赖

二、@Recover(降级)

1. 基本匹配规则

2. @Recover 方法的匹配条件

2.1 异常类型匹配

2.2 返回类型匹配

2.3 参数匹配

3. 常见匹配问题

3.1 模糊匹配错误

3.2 解决方法:更具体的异常类型

4. 最佳实践示例

4.1 清晰的恢复方法命名

4.2 使用不同的返回类型

5. 验证匹配规则

总结


一、@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. 注意事项

  1. 幂等性:确保重试的操作是幂等的

  2. 资源消耗:避免无限重试导致资源耗尽

  3. 超时设置:结合 @Timeout 注解使用

  4. 异常处理:合理设置 value 和 exclude 参数

  5. 性能影响:重试会增加响应时间,需合理配置延迟

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 方法

  • 设计时要避免模糊匹配,使用具体的异常类型和返回类型

更多推荐