自动重试功能在出现异常后重新尝试执行。

5

我已经创建了这个抽象类,以便在出现异常时自动重试网络调用。

  • 我会注意不在InterruptedExceptionUnknownHostException之后重试。
  • 我会重试5次。每次失败后,我会执行指数级退避,从300ms开始增加到1500ms。
public abstract class AutoRetry {

  private Object dataToReturn = null;
  public Object getDataToReturn() {
    return this.dataToReturn;
  }

  public AutoRetry() {

    short retry = -1;
    while (retry++ < StaticData.NETWORK_RETRY) {

      try {
        Thread.sleep(retry * StaticData.NETWORK_CALL_WAIT);
        this.dataToReturn = doWork();
        break;
      } catch (InterruptedException | UnknownHostException e) {
        e.printStackTrace();
        this.dataToReturn = null;
        return;
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  protected abstract Object doWork() throws IOException;
}

我使用它的方式如下:
final Object dataAfterWork = new AutoRetry() {     
  @Override
  protected Object doWork() throws IOException {
    return; //a network call which returns something
  }
}.getDataToReturn();

那么这个实现是否好/正确?


编辑

已移至https://codereview.stackexchange.com/questions/87686


我会使用Java 8的lambda表达式 - 要求一个Supplier<T>并返回T。这将极大地减少样板代码。 - Boris the Spider
1
好主意,但我不能使用Java8。这是一个安卓应用程序。(已添加标签) - Dexter
如果你的代码能够正常工作,我建议你前往这里的代码审查网站 http://codereview.stackexchange.com/ ;) - Ataww
我的代码仍然适用 - 使用匿名类。你的方法有点不好看,因为它包含了null、赋值和break。此外,我的方法中的泛型比你的Object更加简洁。 - Boris the Spider
我的建议是将任务与其执行分开;我会将一个Runnable或自定义接口传递给AutoRetry;这样可以将重试策略与具体的操作解耦。 - Giovanni
@Giovanni,我认为Boris的回答符合了你的建议,对吧? - Dexter
1个回答

2
这看起来相当不错,但我建议将运行任务与重试分开。此外,请使用泛型,不要随意抛出Object
使用Java 8的lambda表达式和方法的return:
public static <T> Optional<T> doWithRetry(final Supplier<T> t) {
    for (int retry = 0; retry <= StaticData.NETWORK_RETRY; ++retry) {
        try {
            Thread.sleep(retry * StaticData.NETWORK_CALL_WAIT);
            return Optional.of(t.get());
        } catch (InterruptedException | UnknownHostException e) {
            LOGGER.log(Level.SEVERE, "Call failed.", e);
            return Optional.empty();
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Call failed. Retry.", e);
        }
    }
    LOGGER.log(Level.SEVERE, "Call failed. Retries exceeded.");
    return Optional.empty();
}

此外,使用一个真正的日志记录器,而不是printStackTrace...
用法:
final String data = doWithRetry(() -> {
   //do stuff 
});

如果您的Lambda表达式需要抛出异常,您需要定义自己的@FunctionalInterface

@FunctionalInterface
interface StuffDoer<T> {
    T doStuff() throws Exception;
}

如果你要在方法签名中使用这个,你需要处理泛型 Exception

Java 8之前的用法:

final String data = doWithRetry(new StuffDoer<T>() {
    @Override
    public T get() throws Exception {
        return null;
    }
});

好的,我可以使用Guava版本的Optional和Supplier。printStackTrace有什么问题吗? - Dexter
1
@Dexter 它会打印到标准输出。你无法控制它打印的位置和时间。在日志记录中使用 STDOUT 是代码不是在专业环境下编写的明显迹象。 - Boris the Spider
好的,那很有道理。但是我正在使用Android Studio,即使是Google的教程也有printStackTrace。(我可以在Android中使用Log) - Dexter
我的整个应用程序到处都有printStackTrace,我是否应该考虑更改它? - Dexter
1
教程通常不会这样做,因为设置日志框架很复杂,而且不是教程的一部分。使用STDOUT“只是有效的”。是的,我会考虑更改它,因为这样你可以关闭所有调试日志记录以进行生产部署 - 你可能不想让人们看到你代码的内部。 - Boris the Spider
现在我想起来了,我经常错过很多的堆栈跟踪..嗯,好的,我懂你的意思 :) - Dexter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接