自定义注解结合AOP实现方法失败重试

明确需求

在开发中,在调用方法时会因为某些原因出现了预料中的异常,而在这些可控的异常时我们想要重新执行该方法。spring提供了实现重试机制的库Spring Retry,我们可以使用这个库优雅的调用重试。

那自己想要实现重试机制,该如何去实现呢;
下面是我使用AOP的AfterThrowing增强和自定义注解来实现简单的重试机制;

定义注解

重试注解接收3个参数:

  • forException: 指定异常,当指定的异常抛出时,执行重试的逻辑
  • fallback:当重试次数达最大时方法依然执行失败的回调
  • maxRetryCount: 最大重试次数,默认值为3次
/**
 * @author luyifo
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Retryable {
    @Nonnull
    Class<? extends Exception> forException();

    @Nonnull
    Class<? extends Fallback> fallback();

    int maxRetryCount() default 3;
}

回调接口定义

这里简单定义了个回调的接口,实际开发中根据需求来定义fallback

/**
 * @author luyifo
 */
public interface Fallback {
    void exec(Object[] args);
}

回调接口实现

/**
 * @author luyifo
 */
public class FallbackImpl implements Fallback{
    @Override
    public void exec(Object[] args) {
        System.out.println("重试回调");
    }
}

重试异常定义

自定义异常,在可以预料中的异常发生时进行抛出,后续在AOP中做判断来执行接下来的重试逻辑

/**
 * @author luyifo
 */
public class RetryException extends Exception{
    public RetryException(String message) {
        super(message);
    }
}

AOP核心

/**
 * @author luyifo
 */
@Component
@Aspect
public class RetryAspect {

    @Pointcut("@annotation(Retryable)")
    public void pt() {}

    @AfterThrowing(pointcut = "pt()", throwing = "ex")
    public void after(JoinPoint jp, Exception ex) {
        System.out.println("after");
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        Retryable retryable = method.getAnnotation(Retryable.class);
        // 异常判断,是否是重试注解指定的异常
        if (ex.getClass() == retryable.forException()) {
            int maxRetryCount = retryable.maxRetryCount();
            int count = 0;
            while (count < maxRetryCount) {
                System.out.println("retry: " + count);
                try {
                    method.invoke(jp.getTarget(), jp.getArgs());
                    // 当重试成功时直接返回
                    return;
                } catch (Exception e) {
                    count++;
                }
            }

            // 当重试次数达到最大值依然失败时执行回调
            Class<? extends Fallback> fallback = retryable.fallback();
            try {
                Fallback instance = fallback.getDeclaredConstructor().newInstance();
                Method exec = fallback.getMethod("exec", Object[].class);
                exec.invoke(instance, new Object[]{jp.getArgs()});
            } catch (Exception e) {
                 System.out.println("回调执行异常");
            }
        }
    }
}

定义全局异常处理

/**
 * @author luyifo
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> defaultExceptionHandle(Exception ex) {
        return ResponseEntity
                .badRequest()
                .body(ex.getMessage());
    }
}

测试

/**
 * @author luyifo
 */
@RestController
public class RetryController {

    @Retryable(forException = RetryException.class,fallback = FallbackImpl.class)
    @GetMapping("/retry")
    public void retry1() throws RetryException {
        throw new RetryException("retry exception");
    }
}

输出结果

after
retry: 0
retry: 1
retry: 2
重试回调

posted on 2024-07-04 10:58  luyifo  阅读(33)  评论(0)    收藏  举报

导航