背景:调用第三方服务,发生特定异常时需要重试
1.封装一层第三方调用的服务类
package com.example.db.service;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class ThirdRpcService {
public Object rpc(Map<String, Object> params) {
return new Object();
}
}
2.自定义一个切面注解
package com.example.db.annotation;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionRetry {
int retryTimes() default 3;
int waitTimes() default 1;
Class[] retryExceptions() default {};
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;
@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();
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);
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();
}
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;
}
}
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.在第三方调用的方法上加上自定义注解
@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;
@EnableAspectJAutoProxy
@SpringBootApplication(scanBasePackages = {"com.example.db"})
public class DbDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DbDemoApplication.class, args);
}
}
6.完整的项目结构截图