注意:
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 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());
@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--) {
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) {
Thread.sleep(waitTime);
}
LOGGER.warn("执行重试切面{}, 还有{}次重试机会, 异常类型{}, 异常信息{}, 栈信息{}", uuid, times, e.getClass().getName(), e.getMessage(), e.getStackTrace());
}
}
return false;
}
}