最近无意间看到了一段代码,实话实说看的我有点难受,刚开始的时候还略微有点懵,只是感觉代码很长。等我捋了一遍之后,发现是一段调用远程接口,失败进行重试功能的代码。代码如下:
方法用到了递归,在重试次数小于零跳出。
说一下存在的问题吧:
- 接口重试和业务本身不发生关系,所以具有很高的耦合性
- 方法采用递归实现,有栈溢出的风险
- 重试逻辑无法进行重用
- 可配置性比较低
看下怎么改一下:
-抽离出重试代码,预留接口,业务代码填入,抽离工具类如下:
public abstract class MyRetryTemplate<T> {
//重试次数
private int retryTime;
//重试时间
private int sleepTime;
//是否倍数增长
private boolean multiple = false;
/**
* 执行业务方法逻辑,由实现类实现
*
* @return
*/
public abstract T remote() throws Exception;
public T execute() throws InterruptedException {
for (int i = 1; i < retryTime + 1; i++) {
try {
return remote();
} catch (Exception e) {
System.out.println(e.getMessage());
if (multiple){
Thread.sleep(sleepTime);
}
else{
Thread.sleep(sleepTime * (i));
}
}
}
return null;
}
public T submit(ExecutorService executorService) {
Future submit = executorService.submit((Callable) () -> execute());
try {
return (T) submit.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
public MyRetryTemplate setRetryTime(int retryTime) {
this.retryTime = retryTime;
return this;
}
public MyRetryTemplate setSleepTime(int sleepTime) {
this.sleepTime = sleepTime;
return this;
}
public MyRetryTemplate setMultiple(boolean multiple) {
this.multiple = multiple;
return this;
}
}
上面的类中,值定义了重试的次数,间隔的时间,和时间的增长参数,预留了remote()抽象方法,交由具体的实现类来实现。类中使用了泛型,支持返回不同的对象。并且支持同步和异步调用。
看下修改后的代码,如下:
是不是感觉一下清爽了很多,这里重试相关的参数,我直接写死在了代码中,可以通过配置文件和数据库配置引入。这个方法应该还能精简,最后两句感觉还是有点多余,由于不知道是干啥的, 就暂且留在这吧。
修改后的代码,虽然解决了一些上面的问题,但是并没有完全解决,代码的侵入性,上面已经说了既然这是重试的逻辑,就不应该出现在代码中。有没有解决的办法呢,有,切面。
下面看下通过切面该怎么实现。
首先定义一个注解:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
int count() default 0 ;
long sleep() default 0 ;
}
定义切面
@Aspect
@Component
public class RetryAspect {
ExecutorService executorService = Executors.newFixedThreadPool(3);
@Around(value = "@annotation(Retry)")
public Object execute(ProceedingJoinPoint point, Retry retry) throws InterruptedException {
System.out.println("----------------- Aspect ---------------------");
MyRetryTemplate myRetryTemplate = new MyRetryTemplate() {
@Override
public ParametersHolder remote() throws Throwable {
return (ParametersHolder) point.proceed();
}
}.setRetryTime(3).setSleepTime(10000).submit(executorService);
return submit;
}
}
最终实现:
在注解中,添加自定义的参数,便可以实现零侵入,也更容易实现方法的复用,如果有其他的业务需要实现重试,直接在业务方法上添加注解即可。
如果不想用这种方法,也可以借助第三方工具guava-retrying和 spring-retry 来实现此功能。