Java重试机制修改

最近无意间看到了一段代码,实话实说看的我有点难受,刚开始的时候还略微有点懵,只是感觉代码很长。等我捋了一遍之后,发现是一段调用远程接口,失败进行重试功能的代码。代码如下:

image.png

方法用到了递归,在重试次数小于零跳出。
说一下存在的问题吧:

  • 接口重试和业务本身不发生关系,所以具有很高的耦合性
  • 方法采用递归实现,有栈溢出的风险
  • 重试逻辑无法进行重用
  • 可配置性比较低

看下怎么改一下:

-抽离出重试代码,预留接口,业务代码填入,抽离工具类如下:

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()抽象方法,交由具体的实现类来实现。类中使用了泛型,支持返回不同的对象。并且支持同步和异步调用。

看下修改后的代码,如下:

image.png

是不是感觉一下清爽了很多,这里重试相关的参数,我直接写死在了代码中,可以通过配置文件和数据库配置引入。这个方法应该还能精简,最后两句感觉还是有点多余,由于不知道是干啥的, 就暂且留在这吧。

修改后的代码,虽然解决了一些上面的问题,但是并没有完全解决,代码的侵入性,上面已经说了既然这是重试的逻辑,就不应该出现在代码中。有没有解决的办法呢,有,切面。

下面看下通过切面该怎么实现。

首先定义一个注解:

@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;
    }
}

最终实现:

image.png

在注解中,添加自定义的参数,便可以实现零侵入,也更容易实现方法的复用,如果有其他的业务需要实现重试,直接在业务方法上添加注解即可。

如果不想用这种方法,也可以借助第三方工具guava-retrying和 spring-retry 来实现此功能。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容