如何使用java 8功能简化重试代码块

时间:2016-06-29 17:11:30

标签: java java-8

在我的代码中,我有一个试图连接到某个外部接口的部分,如果失败,那么它将重试它固定的次数。代码有效,但有点难看。我想知道这是否可以使用一些奇特的Java8功能以更优雅的方式完成?

int count = 0;
final int maxRetries = 3;
while ( count < maxRetries )
{
   try
   {
     // Some Code
     // break out of loop on success
   }
   catch ( final ExecutionException e )
   {
      LOG.debug( "retrying..." );
      if ( ++count >= maxRetries )
      {
         LOG.debug( "do something else...");
         //do something else
      }
   }
}

5 个答案:

答案 0 :(得分:16)

您可以做的是分离重试逻辑。你需要一些辅助脚手架:

interface ThrowingTask {
    void run() throws ExecutionException;
}

现在,你写道:

boolean runWithRetries(int maxRetries, ThrowingTask t) { 
    int count = 0;
    while (count < maxRetries) {
        try {
            t.run();
            return true;
        }
        catch (ExecutionException e) {
            if (++count >= maxRetries)
                return false;
        }
    }
}

现在,您可以通过重试来运行,而无需将任务逻辑与重试逻辑混淆:

runWithRetries(MAX_RETRIES, () -> { /* do stuff */ });

你可以调整它,因为你喜欢接受在重试时调用的lambda,返回重试计数等等。但游戏是编写像runWithRetries这样的方法来捕获控制流但是抽象的是什么行为需要完成 - 所以你只需要编写一次重试循环,然后在需要的地方填写你想要的实际行为。

答案 1 :(得分:7)

使用Failsafe

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(ExecutionException.class)
  .withMaxRetries(3);

Failsafe.with(retryPolicy)
  .onRetry(r -> LOG.debug("retrying..."))
  .withFallback(e -> LOG.debug("do something else..."))
  .run(() -> someCode());

它与您的用例一样简单而富有表现力。

答案 2 :(得分:6)

嗯,我认为更多功能性的方法是使用Try monad,遗憾的是jdk 8中不适合我们:(

尽管如此,您仍然可以使用提供它的better-monads library。有了这个,你可以想出一些像这样的实现:

public static <Out> Try<Out> tryTimes(int times, TrySupplier<Out> attempt) {
        Supplier<Try<Out>> tryAttempt = () -> Try.ofFailable(attempt::get);

        return IntStream.range(1, times)
                .mapToObj(i -> tryAttempt)
                .reduce(tryAttempt, (acc, current) -> () -> acc.get().recoverWith(error -> current.get()))
                .get();
    }

长话短说这个函数只是链接tryAttempt的调用,并且在尝试失败的情况下尝试recoverWith下一次调用tryAttempt。客户端代码将如下所示:

tryTimes(10, () -> {
            // all the logic to do your possibly failing stuff
        }
);

结果客户端代码将获得Try<T>,可以通过直接调用.get()来解压缩(如果成功返回值,如果失败则抛出基础异常)或其他库文档中描述的方法。

希望它有所帮助。

<强>更新

这也可以使用filterfindFirstlimit以及没有任何外部库的功能性方式完成:

interface ThrowingSupplier<Out> { Out supply() throws Exception; }

public static <Out> Optional<Out> tryTimes(int times, ThrowingSupplier<Out> attempt) {
    Supplier<Optional<Out>> catchingSupplier = () -> {
        try {
            return Optional.ofNullable(attempt.supply());
        } catch (Exception e) {
            return Optional.empty();
        }
    };
    return Stream.iterate(catchingSupplier, i -> i)
            .limit(times)
            .map(Supplier::get)
            .filter(Optional::isPresent)
            .findFirst()
            .flatMap(Function.identity());
}

客户端代码保持不变。此外,请注意,它不会评估表达式times次,但会在第一次成功尝试时停止。

答案 3 :(得分:1)

与某些建议的方法类似,您可以创建一个类,将重试功能与其余代码分开。下面的类就是这样,也允许你接受一个输入并返回一些结果:

public class Retrier<T, R> {
    private static final int DEFAULT_RETRY_COUNT = 3;
    private int retryCount;
    private Function<T, R> retriable;
    private T input;

    public Retrier(T input, Function<T, R> retriable) {
        this(input, retriable, DEFAULT_RETRY_COUNT);
    }

    public Retrier(T input, Function<T, R> retriable, int retryCount) {
        this.retryCount = retryCount;
        this.retriable = retriable;
        this.input = input;
    }

    public R execute() {
        int count = 0;
        while(true) {
            try {
                return retriable.apply(input);
            }
            catch (Exception e) {
                if (++count >= retryCount) {
                    throw e;
                }
            }
        }
    }
}

然后你可以像这样使用它。

String result = new Retrier<String, String>("input", input -> {/* process the input and return the result*/}).execute();

无论您的代码抛出异常,都会重试。

答案 4 :(得分:0)

您可以尝试此静态方法

/**
 * Author: Mak Sophea
 * Date: 07/17/2020
 */
public class RetryUtils {
    private static final Logger log = LoggerFactory.getLogger(RetryUtils.class);

    public static interface CallToRetry {
        void process() throws Exception;
    }

    public static boolean withRetry(int maxTimes, long intervalWait, CallToRetry call) throws Exception {
        if (maxTimes <= 0) {
            throw new IllegalArgumentException("Must run at least one time");
        }
        if (intervalWait <= 0) {
            throw new IllegalArgumentException("Initial wait must be at least 1");
        }
        Exception thrown = null;
        for (int i = 0; i < maxTimes; i++) {
            try {
                call.process();
                return true;
            } catch (Exception e) {
                thrown = e;
                log.info("Encountered failure on {} due to {}, attempt retry {} of {}", call.getClass().getName() , e.getMessage(), (i + 1), maxTimes, e);
            }
            try {
                Thread.sleep(intervalWait);
            } catch (InterruptedException wakeAndAbort) {
                break;
            }
        }
        throw thrown;
    }
}

您可以通过以下方式调用它 RetryUtils.withRetry(MAX_RETRY,3000,()-> {//将实现放在这里});