基于结果而非异常重试一个方法

17

我有一个拥有以下签名的方法:

public Optional<String> doSomething() {
    ...
}
如果我得到一个空的Optional,我想重试这个方法,并在重试了3次后才返回空的Optional
我查找并找到了Retryable Spring注释,但它似乎只适用于异常。
如果可能的话,我想使用一个库来实现这个功能,并避免:
  • 创建和抛出异常。
  • 自己编写逻辑。

1
我指的更多是注释,而不是直接的Java。 - orirab
你为什么一定要在第一时间返回一个 Optional 呢?我的意思是,既然 @Retryable 可以处理抛出的异常,你完全可以自己创建一个异常,在未获取到值的情况下抛出它,然后重试操作。这样你就可以节省一些时间,不用编写自己的注释等内容了。 - akortex
1
抛出异常的性能代价很高,例如在这里查看 https://dev59.com/n3VC5IYBdhLWcg3wbgdS - orirab
“过早的优化是万恶之源” --- 道格拉斯·克努斯:《计算机程序设计艺术》(1974),第671页 - Turing85
1
这不是过早优化,无论如何你都不应该不必要地使用异常。 - orirab
显示剩余3条评论
4个回答

7

我一直在使用内置重试的 failsafe。你可以基于谓词和异常来重试。

您的代码将如下所示:

    private Optional<String> doSomethingWithRetry() {
        RetryPolicy<Optional> retryPolicy = new RetryPolicy<Optional>()
                .withMaxAttempts(3)
                .handleResultIf(result -> {
                    System.out.println("predicate");
                    return !result.isPresent();
                });

        return Failsafe
                .with(retryPolicy)
                .onSuccess(response -> System.out.println("ok"))
                .onFailure(response -> System.out.println("no ok"))
                .get(() -> doSomething());
    }

    private Optional<String> doSomething() {
         return Optional.of("result");
    }

如果可选项不为空,则输出为:
predicate
ok

否则看起来像:
predicate
predicate
predicate
no ok

我稍微看了一下这个框架(非常可爱),但是我还没有找到实现我所要求的方法的方式。据我所知,你的代码意味着如果我的方法在第一次尝试时返回一个空的 Optional,它将不会重试。如果它是空的,我想重试直到它不为空,或者重试了 3 次。 - orirab
嗨@orirab。我更新了答案,并提供了一个经过测试的示例。 - Cristian Rodriguez
我会试一下,看起来非常不错。如果它运行良好,我可能会接受你的答案。谢谢! - orirab

3

@Retryable(以及基础的RetryTemplate)完全基于异常。

您可以子类化RetryTemplate,覆盖doExecute()以检查返回值。

您可能需要复制方法中的大部分代码;它实际上并不是为了仅覆盖retryCallback.doWithRetry()调用而设计的。

您可以在@Retryableinterceptor属性中指定一个自定义的RetryOperationsInterceptor来使用自定义的RetryTemplate

编辑

当前的RetryTemplate代码如下...

while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

    try {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
        }
        // Reset the last exception, so if we are successful
        // the close interceptors will not think we failed...
        lastException = null;
        return retryCallback.doWithRetry(context);
    }
    catch (Throwable e) {

        lastException = e;

        try {
            registerThrowable(retryPolicy, state, context, e);
        }
        catch (Exception ex) {
            throw new TerminatedRetryException("Could not register throwable",
                    ex);
        }
        finally {
            doOnErrorInterceptors(retryCallback, context, e);
        }

         ... 

    }

您需要将其更改为类似以下内容的形式...
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

    try {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
        }
        // Reset the last exception, so if we are successful
        // the close interceptors will not think we failed...
        lastException = null;
        T result = retryCallback.doWithRetry(context);
        if (((Optional<String>) result).get() == null) {

            try {
                registerThrowable(retryPolicy, state, context, someDummyException);
            }
            catch (Exception ex) {
                throw new TerminatedRetryException("Could not register throwable",
                        ex);
            }
            finally {
                doOnErrorInterceptors(retryCallback, context, e);
            }

            ...
        }
        else {
            return result;
        }
    }
    catch (Throwable e) {

       ...

    }

其中someDummyException只是为了欺骗上下文而增加计数器。它可以是一个static字段,仅创建一次即可。


2

我目前已经自己编写了一个工具(使用纯Java),当然欢迎其他答案:

Original Answer翻译成"最初的回答"

import java.util.function.Predicate;
import java.util.function.Supplier;

public class Retryable<T> {
    private Supplier<T> action = () -> null;
    private Predicate<T> successCondition = ($) -> true;
    private int numberOfTries = 3;
    private long delay = 1000L;
    private Supplier<T> fallback = () -> null;

    public static <A> Retryable<A> of(Supplier<A> action) {
        return new Retryable<A>().run(action);
    }

    public Retryable<T> run(Supplier<T> action) {
        this.action = action;
        return this;
    }

    public Retryable<T> successIs(Predicate<T> successCondition) {
        this.successCondition = successCondition;
        return this;
    }

    public Retryable<T> retries(int numberOfTries) {
        this.numberOfTries = numberOfTries;
        return this;
    }

    public Retryable<T> delay(long delay) {
        this.delay = delay;
        return this;
    }

    public Retryable<T> orElse(Supplier<T> fallback) {
        this.fallback = fallback;
        return this;
    }

    public T execute() {
        for (int i = 0; i < numberOfTries; i++) {
            T t = action.get();
            if (successCondition.test(t)) {
                return t;
            }

            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                // do nothing
            }
        }
        return fallback.get();
    }
}

使用这段代码后,我的方法如下所示:


最初的回答

public Optional<String> doSomething() {
    return Retryable
        .of(() -> actualDoSomething())
        .successIs(Optional::isPresent)
        .retries(3)
        .delay(1000L)
        .orElse(Optional::empty)
        .execute();
}

0

如果你的结果不符合预期,就抛出异常


4
这个回答似乎和“你应该改进你的回答”一样有用。虽然我可能有点夸张... - Yunnosch

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