基于aop+注解方式实现异常重试机制
背景:调用第三方服务,发生特定异常时需要重试
1.封装一层第三方调用的服务类
package com.example.db.service;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @author Vick C
*/
@Service
public class ThirdRpcService {
/**
* 示例方法
* <P>可以是依赖jdk的封装,也可以是自己实现的封装</P>
* @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.在第三方调用的方法上加上自定义注解
/**
* 示例方法
* <P>可以是依赖jdk的封装,也可以是自己实现的封装</P>
* @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.完整的项目结构截图
摘抄自网络,便于检索查找。