Повторите метод, основанный на результате (вместо исключения) - PullRequest
0 голосов
/ 04 февраля 2019

У меня есть метод со следующей подписью:

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

Если я получу пустое Optional Я бы хотел повторить этот метод и только после 3 раз вернуть пустое Optional.

Я посмотрел и нашел аннотацию Retryable spring, но, похоже, она работает только для исключений.

Если возможно, я бы хотел использовать для этого библиотеку и избегать:

  • Создание и создание исключения.
  • Сама пишу логику.

Ответы [ 3 ]

0 голосов
/ 04 февраля 2019

Я сам написал утилиту для этого (vanilla java), другие ответы приветствуются:

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 голосов
/ 15 августа 2019

Я использовал failsafe build in retry.Вы можете повторить попытку на основе предикатов и исключений.

Ваш код будет выглядеть следующим образом:

    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
0 голосов
/ 04 февраля 2019

@Retryable (и базовый RetryTemplate) основаны исключительно на исключениях.

Вы можете создать подкласс RetryTemplate, переопределяя doExecute(), чтобы проверить возвращаемое значение.

Вывероятно, придется копировать большую часть кода в методе;на самом деле он не предназначен для переопределения только вызова retryCallback.doWithRetry().

Вы можете использовать пользовательский RetryTemplate в RetryOperationsInterceptor (указанный в @Retryable в свойстве interceptor).

РЕДАКТИРОВАТЬ

Текущий код 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, созданное один раз.

...