Retry a method based on result (instead of exception)

丶灬走出姿态 提交于 2020-07-06 11:04:47

问题


I have a method with the following signature:

public Optional<String> doSomething() {
    ...
}

If I get an empty Optional I'd like to retry this method and only after 3 times return the empty Optional.

I've looked and found the Retryable spring annotation, but it seems to only work on Exceptions.

If possible I'd like to use a library for this, and avoid:

  • Creating and throwing an exception.
  • Writing the logic myself.

回答1:


I have been using failsafe build in retry. You can retry based on predicates and exceptions.

Your code would look like this:

    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");
    }

If the optional is not empty the output is:

predicate
ok

Otherwise looks like:

predicate
predicate
predicate
no ok



回答2:


@Retryable (and the underlying RetryTemplate) are purely based on exceptions.

You could subclass RetryTemplate, overriding doExecute() to check the return value.

You would probably have to replicate much of the code in the method; it's not really designed for overriding just the retryCallback.doWithRetry() call.

You can use a custom RetryTemplate in a RetryOperationsInterceptor (specified in the @Retryable in the interceptor property).

EDIT

The current RetryTemplate code looks like this...

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

         ... 

    }

You would need to change it to something like...

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) {

       ...

    }

Where someDummyException is to fool the context into incrementing the counter. It can be a static field, just created once.




回答3:


I currently wrote a util for this myself (vanilla java), other answers are more than welcome:

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();
    }
}

With this code, my method looks like this:

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



回答4:


Just throw Exception if your result is not desired



来源:https://stackoverflow.com/questions/54521486/retry-a-method-based-on-result-instead-of-exception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!