两种通过aop设置重试机制的方式

注意:

1.不要在同一个类中调用自定义的注解,如果controller调用,注解要放在service层(其他类)

2.如果有配置aop扫描的包,不能只设置扫描control下的文件

 

方式一:

/**
* controller层
*/
@RequestMapping(value = "/feedback/test", method = RequestMethod.POST)
    public void test() throws Exception{

        /**
         * 不能在本类中调用带注解的方法
         *
         * 先进入方法再进入切面
         */
        demoService.genBigNum();
    }
/**
* service层
*/
@Component
public class DemoService {

    @Retry(count = 5, sleep = 2 * 1000)
    public int genBigNum() throws Exception {
        int a = (int) (Math.random() * 10);
        System.out.println("genBigNum " + a);
        if (a < 300) {
            throw new Exception("num less than 300");
        }

        return a;
    }
}
/**
 * 自定义注解
 *
 * @author leon
 * @date 2019/08/19
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
    /**
     * 重试次数
     * @return
     */
    int count() default 0;


    /**
     * 重试的间隔时间
     * @return
     */
    int sleep() default 0;


    /**
     * 是否支持异步重试方式
     * @return
     */
    boolean asyn() default false;
}
/**
 * 模版
 *
 * @author leon
 * @date 2019/08/19
 */
public abstract class RetryTemplate {

    private static final int DEFAULT_RETRY_TIME = 1;

    private int retryTime = DEFAULT_RETRY_TIME;

    private static final Logger LOGGER = LoggerFactory.getLogger(RetryTemplate.class);

    // 重试的睡眠时间
    private int sleepTime = 0;

    public int getSleepTime() {
        return sleepTime;
    }

    public RetryTemplate setSleepTime(int sleepTime) {
        if(sleepTime < 0) {
            throw new IllegalArgumentException("sleepTime should equal or bigger than 0");
        }

        this.sleepTime = sleepTime;
        return this;
    }

    public int getRetryTime() {
        return retryTime;
    }

    public RetryTemplate setRetryTime(int retryTime) {
        if (retryTime <= 0) {
            throw new IllegalArgumentException("retryTime should bigger than 0");
        }

        this.retryTime = retryTime;
        return this;
    }

    /**
     * 留给业务方实现
     * 重试的业务执行代码
     * 失败时请抛出一个异常
     *
     * todo 确定返回的封装类,根据返回结果的状态来判定是否需要重试
     *
     * @return
     */
    protected abstract Object doBiz() throws Throwable;


    public Object execute() throws InterruptedException {
        for (int i = 0; i < retryTime; i++) {
            try {
                return doBiz();
            } catch (Throwable e) {
                LOGGER.error("业务执行出现异常,e: {}", e);
                Thread.sleep(sleepTime);
            }
        }

        return null;
    }


    public Object submit(ExecutorService executorService) {
        if (executorService == null) {
            throw new IllegalArgumentException("please choose executorService!");
        }

        return executorService.submit((Callable) () -> execute());
    }

}
/**
 * 切面
 *
 * @author leon
 * @date 2019/08/19
 */
@Aspect
@Component
@Slf4j
public class RetryAspect {

    /**
     * 注意:此处使用了无界队列(LinkedBlockingQueue)
     */
    ExecutorService executorService = new ThreadPoolExecutor(3, 5,
        1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

    @Around(value = "@annotation(retry)")
    public Object execute(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable {
        RetryTemplate retryTemplate = new RetryTemplate() {
            @Override
            protected Object doBiz() throws Throwable{
                return joinPoint.proceed();
            }
        };

        retryTemplate.setRetryTime(retry.count())
            .setSleepTime((int)retry.sleep());


        if (retry.asyn()) {
            return retryTemplate.submit(executorService);
        } else {
            return retryTemplate.execute();
        }
    }
}

 

 

方法2:

    /**
     * controller层
     *
     * 直接进入切面,再返回到方法
     * @throws Exception
     */
    @RequestMapping(value = "/feedback/test1", method = RequestMethod.POST)
    @RetryDot(times = 5, waitTime = 2 * 1000)
    public void aa() throws Exception{

       String ss = null;
       ss.length();
    }
/**
 * 自定义注解
 * <用来异常重试>
 * 注意:needThrowExceptions & catchExceptions 是相关联的, 有顺序依赖。
 * 当这两个数组的长度都为0时, 直接执行重试逻辑。
 *
 * @author zmc
 * @date 2019/08/19
 * @see ExceptionRetryAspect
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryDot {
    /**
     * 设置失败之后重试次数,默认为1次。
     * 少于1次,则默认为1次
     * 推荐最好不要超过5次, 上限为10次
     * 当没有重试次数时, 会将异常重新抛出用来定位问题。
     *
     * @return
     */
    int times() default 1;

    /**
     * 重试等待时间,时间单位为毫秒。默认是 0.5 * 1000ms, 小于等于0则不生效
     * 推荐不要超过 3 * 1000ms
     * 上限为 10 * 1000ms
     *
     * @return
     */
    long waitTime() default 500;

    /**
     * 需要抛出的异常, 这些异常发生时, 将直接报错, 不再重试。
     * 传入一些异常的class对象
     * 如UserException.class
     * 当数组长度为0时, 那么都不会抛出, 会继续重试
     *
     * @return 异常数组
     */
    Class[] needThrowExceptions() default {};

    /**
     * 需要捕获的异常, 如果需要捕获则捕获重试。否则抛出异常
     * 执行顺序 needThrowExceptions --> catchExceptions 两者并不兼容
     * 当 needThrowExceptions 判断需要抛出异常时, 抛出异常, 否则进入此方法, 异常不在此数组内则抛出异常
     * 当数组长度为0时, 不会执行捕获异常的逻辑。
     *
     * @return 异常数组
     */
    Class[] catchExceptions() default {};


    /**
     * 是否支持异步重试方式
     * @return
     */
    boolean asyn() default false;
/**
 * 切面
 * controller(调用之后)会直接进入该切面;
 * 跑到joinPoint.proceed()才回到业务层
 * 捕获异常之后再回到该切面进行循环重试
 *
 * <异常重试切面>
 *
 * @author leon
 * @date 2019/08/19
 */
@Aspect
@Component
public class ExceptionRetryAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionRetryAspect.class);

    @Pointcut("@annotation(com.fangdd.vr.ip.fdd.server.annotate.RetryDot)")
    public void retryPointCut() {
    }

    @Around("retryPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        LOGGER.info("进入切面...");
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RetryDot retry = method.getAnnotation(RetryDot.class);
        String name = method.getName();
        Object[] args = joinPoint.getArgs();
        String uuid = UUID.randomUUID().toString();
        LOGGER.info("执行重试切面{}, 方法名称{}, 方法参数{}", uuid, name, JSON.toJSONString(args));
        int times = retry.times();
        long waitTime = retry.waitTime();
        Class[] needThrowExceptions = retry.needThrowExceptions();
        Class[] catchExceptions = retry.catchExceptions();
        // check param
        if (times <= 0) {
            times = 1;
        }

        for (; times >= 0; times--) {
            try {
                return joinPoint.proceed();
            } catch (Exception e) {
                // 如果需要抛出的异常不是空的, 看看是否需要抛出
                if (needThrowExceptions.length > 0) {
                    for (Class exception : needThrowExceptions) {
                        if (exception == e.getClass()) {
                            LOGGER.warn("执行重试切面{}失败, 异常在需要抛出的范围{}, 业务抛出的异常类型{}", uuid, needThrowExceptions,
                                e.getClass().getName());
                            throw e;
                        }
                    }
                }

                // 如果需要抛出异常,而且需要捕获的异常为空那就需要再抛出
                if (catchExceptions.length > 0) {
                    boolean needCatch = false;
                    for (Class catchException : catchExceptions) {
                        if (e.getClass() == catchException) {
                            needCatch = true;
                            break;
                        }
                    }
                    if (!needCatch) {
                        LOGGER.warn("执行重试切面{}失败, 异常不在需要捕获的范围内, 需要捕获的异常{}, 业务抛出的异常类型{}", uuid, catchExceptions,
                            e.getClass().getName());
                        throw e;
                    }
                }

                // 如果接下来没有重试机会的话,直接报错
                if (times <= 0) {
                    LOGGER.warn("执行重试切面{}失败", uuid);
                    throw e;
                }

                // 休眠 等待下次执行
                if (waitTime > 0) {
                    Thread.sleep(waitTime);
                }

                LOGGER.warn("执行重试切面{}, 还有{}次重试机会, 异常类型{}, 异常信息{}, 栈信息{}", uuid, times, e.getClass().getName(), e.getMessage(), e.getStackTrace());
            }
        }
        return false;
    }
}

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值