基于aop+注解方式实现异常重试机制

背景:调用第三方服务,发生特定异常时需要重试

1.封装一层第三方调用的服务类

package com.example.db.service;

import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * @author Vick C
 */
@Service
public class ThirdRpcService {

    /**
     * 示例方法
     * 

可以是依赖jdk的封装,也可以是自己实现的封装

* @param params 请求参数 * @return 调用响应结果 */
public Object rpc(Map<String, Object> params) { // 省略调用逻辑 // 返回调用结果 return new Object(); } }

2.自定义一个切面注解

package com.example.db.annotation;

import java.lang.annotation.*;

/**
 * @author Vick C
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionRetry {

    /**
     * @return 重试次数
     */
    int retryTimes() default 3;

    /**
     * @return 重试间隔时间
     */
    int waitTimes() default 1;

    /**
     * @return 需要重试的异常类型
     */
    Class[] retryExceptions() default {};

    /**
     * @return 不重试直接抛出的异常
     */
    Class[] throwExceptions() default {};
}

3.定义注解切面

package com.example.db.aop;

import com.example.db.annotation.ExceptionRetry;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;


/**
 * @author Vick C
 */
@Aspect
@Component
public class ExceptionRetryAspect {

    private static final Logger log = LoggerFactory.getLogger(ExceptionRetryAspect.class);

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.example.db.annotation.ExceptionRetry)")
    public void retryPointCut() {
    }

    @Around("retryPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        ExceptionRetry retry = method.getAnnotation(ExceptionRetry.class);
        String name = method.getName();
        Object[] args = joinPoint.getArgs();
        String uuid = new Random().toString();
        log.info("执行重试切面{}, 方法名称{}, 方法参数{}", uuid, name, toJson(args));
        int times = retry.retryTimes();
        long waitTime = retry.waitTimes();
        Class[] needRetryExceptions = retry.retryExceptions();
        Class[] throwExceptions = retry.throwExceptions();
        // check param
        if (times <= 0) {
            times = 1;
        }
        if (waitTime < 0) {
            waitTime = 0;
        }

        for (; times >= 0; times--) {
            try {
                return joinPoint.proceed();
            } catch (Exception e) {
                // 需要重试异常处理
                needRetryExceptionHandle(needRetryExceptions, uuid, e);
                // 需要抛出异常处理
                throwExceptionHandle(throwExceptions, uuid, e);
                // 重试次数为0或负数,直接报错
                if (times <= 0) {
                    log.warn("执行重试切面{}失败", uuid);
                    throw e;
                }
                // 休眠 等待下次执行
                if (waitTime > 0) {
                    Thread.sleep(waitTime * 1000);
                }

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

    /**
     * 指定抛出异常处理
     * @param throwExceptions 指定异常类型
     * @param uuid            uuid标识
     * @param e               当前异常
     * @throws Exception 异常
     */
    private void throwExceptionHandle(Class[] throwExceptions, String uuid, Exception e) throws Exception {
        if (throwExceptions.length <= 0) {
            return;
        }
        boolean needCatch = false;
        for (Class catchException : throwExceptions) {
            if (e.getClass() == catchException) {
                needCatch = true;
                break;
            }
        }
        if (!needCatch) {
            log.warn("执行重试切面{}失败, 异常不在需要捕获的范围内, 需要捕获的异常{}, 业务抛出的异常类型{}", uuid, throwExceptions, e.getClass().getName());
            throw e;
        }
    }

    /**
     * 需要重试异常
     * @param needRetryExceptions 指定需要重试的异常类型
     * @param uuid                uuid标识
     * @param e                   当前异常
     * @throws Exception 异常
     */
    private void needRetryExceptionHandle(Class[] needRetryExceptions, String uuid, Exception e) throws Exception {
        if (needRetryExceptions.length <= 0) {
            return;
        }

        for (Class exception : needRetryExceptions) {
            if (exception != e.getClass()) {
                continue;
            }
            log.warn("执行重试切面{}失败, 异常在需要抛出的范围{}, 业务抛出的异常类型{}", uuid, needRetryExceptions, e.getClass().getName());
            throw e;
        }
    }

    private String toJson(Object obj) throws JsonProcessingException {
        return objectMapper.writeValueAsString(obj);
    }
}

4.在第三方调用的方法上加上自定义注解

    /**
     * 示例方法
     * 

可以是依赖jdk的封装,也可以是自己实现的封装

* @param params 请求参数 * @return 调用响应结果 */
@ExceptionRetry(retryTimes = 5, waitTimes = 2, retryExceptions = ArithmeticException.class, throwExceptions = NullPointerException.class) public Object rpc(Map<String, Object> params) { // 省略调用逻辑 // 返回调用结果 return new Object(); }
@ExceptionRetry(retryTimes = 5, waitTimes = 2, retryExceptions = ArithmeticException.class, throwExceptions = NullPointerException.class)
说明:
retryTimes=5表示重试5次
waitTimes=2表示重试间隔2秒
retryExceptions = ArithmeticException.class表示遇到ArithmeticException则重试
throwExceptions = NullPointerException.class表示遇到NullPointerException则抛出异常不重试

5.在启动类上加上注解@EnableAspectJAutoProxy

package com.example.db;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author Vick
 */
@EnableAspectJAutoProxy
@SpringBootApplication(scanBasePackages = {"com.example.db"})
public class DbDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DbDemoApplication.class, args);
    }

}

6.完整的项目结构截图

基于aop+注解方式实现异常重试机制_第1张图片

你可能感兴趣的:(Java,aop,annotation,java)