Spring Boot AOP 优雅实现异常重试机制

Spring Boot AOP 优雅实现异常重试机制

零侵入、可配置、易扩展

通过 自定义注解 + AOP 环绕通知 在任意方法上实现 异常自动重试 ,支持
重试次数、间隔、异常类型、快速失败 等高级特性。


一、实现效果

java 复制代码
@Service
public class RemoteService {

    @Retryable(retryTimes = 3, interval = 2_000,
               retryOn = ConnectException.class,
               fastFailOn = IllegalArgumentException.class)
    public String call() {
        // 调用第三方接口
        return restTemplate.getForObject(url, String.class);
    }
}
  • 发生异常后自动重试 3 次,每次间隔 2 秒
  • 出现 IllegalArgumentException 立即结束重试
  • 成功后直接返回结果,对业务代码 零侵入

二、核心步骤

1. 引入依赖(Spring Boot 3.x)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 定义注解

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retryable {
    int retryTimes() default 3;          // 最大重试次数
    long interval() default 1_000;       // 间隔毫秒
    Class<? extends Throwable>[] retryOn() default {}; // 需要重试的异常
    Class<? extends Throwable>[] fastFailOn() default {}; // 立即失败的异常
}

3. AOP 切面(RetryableAspect)

java 复制代码
@Slf4j
@Aspect
@Component
public class RetryableAspect {

    @Around("@annotation(retryable)")
    public Object around(ProceedingJoinPoint pjp, Retryable retryable) throws Throwable {
        int maxAttempts = retryable.retryTimes();
        long interval    = retryable.interval();
        Set<Class<?>> retrySet   = Set.of(retryable.retryOn());
        Set<Class<?>> failSet    = Set.of(retryable.fastFailOn());

        Throwable lastEx = null;
        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                return pjp.proceed();
            } catch (Throwable ex) {
                lastEx = ex;
                // 快速失败
                if (failSet.stream().anyMatch(c -> c.isAssignableFrom(ex.getClass()))) {
                    log.error("Fast fail on {}", ex.getClass().getSimpleName());
                    throw ex;
                }
                // 最后一轮不再重试
                if (attempt == maxAttempts) {
                    log.error("Exhausted {} attempts", maxAttempts);
                    throw ex;
                }
                // 匹配重试异常
                if (retrySet.isEmpty() || retrySet.stream().anyMatch(c -> c.isAssignableFrom(ex.getClass()))) {
                    log.warn("Retry {}/{} after {}ms caused by {}", attempt, maxAttempts, interval, ex.toString());
                    Thread.sleep(interval);
                } else {
                    throw ex;
                }
            }
        }
        throw new IllegalStateException("Should never reach here", lastEx);
    }
}

4. 在启动类开启 AOP

java 复制代码
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

三、高级扩展(可选)

特性 实现方式
指数退避 interval 改为 (long) (interval * Math.pow(2, attempt-1))
重试日志追踪 利用 MDC 注入 traceId,方便链路追踪
异步重试 Thread.sleep 换成 CompletableFuture.delayedExecutor
Spring Retry 集成 使用 @EnableRetry + @Retryable(开箱即用,但缺少自定义策略)

四、测试用例

java 复制代码
@SpringBootTest
class RetryableTest {

    @Autowired RemoteService service;

    @Test
    void shouldRetryAndFinallySuccess() {
        // 第 1 次失败,第 2 次成功
        String result = service.call();
        assertThat(result).isEqualTo("success");
    }
}

五、一句话总结

一个注解 + 一个切面 = 零侵入、可配置、易维护的异常重试机制

网络抖动、偶发异常 不再影响业务稳定性,真正做到 优雅重试