onent
public class CGLibRetryProxyHandler implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
int times = 1;
while (times <= 5) {
try {
// 故意抛异常
int i = 3 / 0;
return method.invoke(target, objects);
} catch (Exception e) {
System.out.println("重试【" + times + "】次");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
return null;
}
public Object getCglibProxy(Object objectTarget){
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);
Object result = enhancer.create();
return result;
}
}
测试:
@GetMapping("/addOrder")
public String addOrder() {
OrderService orderServiceProxy = (OrderService) cgLibRetryProxyHandler.getCglibProxy(orderService);
orderServiceProxy.addOrder();
return "addOrder";
}
这样就很棒了,完美的解决了 JDK 动态代理带来的缺陷。优雅指数上涨了不少。
但这个方案仍旧存在一个问题,那就是需要对原来的逻辑进行侵入式修改,在每个被代理实例被调用的地方都需要进行调整,这样仍然会对原有代码带来较多修改
5. 手动 Aop
考虑到以后可能会有很多的方法也需要重试功能,咱们可以将重试这个共性功能通过 AOP 来实现,使用 AOP 来为目标调用设置切面,即可在目标方法调用前后添加一些重试的逻辑
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
自定义注解:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRetryable {
// 最大重试次数
int retryTimes() default 3;
// 重试间隔
int retryInterval() default 1;
}
@Slf4j
@Aspect
@Component
public class RetryAspect {
@Pointcut("@annotation(com.hcr.sbes.retry.annotation.MyRetryable)")
private void retryMethodCall(){}
@Around("retryMethodCall()")
public Object retry(ProceedingJoinPoint joinPoint) throws InterruptedException {
// 获取重试次数和重试间隔
MyRetryable retry = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(MyRetryable.class);
int maxRetryTimes = retry.retryTimes();
int retryInterval = retry.retryInterval();
Throwable error = new RuntimeException();
for (int retryTimes = 1; retryTimes <= maxRetryTimes; retryTimes++){
try {
Object result = joinPoint.proceed();
return result;
} catch (Throwable throwable) {
error = throwable;
log.warn("调用发生异常,开始重试,retryTimes:{}", retryTimes);
}
Thread.sleep(retryInterval * 1000L);
}
throw new RuntimeException("重试次数耗尽", error);
}
}
给需要重试的方法添加注解 @MyRetryable
:
@Service
public class OrderServiceImpl implements OrderService {
@Override
@MyRetryable(retryTimes = 5, retryInterval = 2)
public void addOrder() {
int i = 3 / 0;
// addOrder
}
}
这样即不用编写重复代码,实现上也比较优雅了:一个注解就实现重试。
6. spring-retry
Spring Boot 基础就不介绍了,推荐看这个实战项目:
https://github.com/javastacks/spring-boot-best-practice
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
开启重试功能:在启动类或者配置类上添加 @EnableRetry
注解
在需要重试的方法上添加 @Retryable
注解
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2