Повторить метод на основе результата (вместо исключения)

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

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

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

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

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

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

person orirab    schedule 04.02.2019    source источник
comment
Вы можете использовать рекурсию и отслеживать, сколько попыток было предпринято до сих пор, или вызвать doSomething() из другого метода, который отслеживает.   -  person Ethan Roseman    schedule 04.02.2019
comment
Я имел в виду больше по строкам аннотации, а не прямо java   -  person orirab    schedule 04.02.2019
comment
Я не знаю библиотеки, которая делала бы именно то, что вам нужно, но было бы просто написать свою собственную аннотацию для этого, а затем повторно использовать ее по мере необходимости в своем коде.   -  person Nicholas Hirras    schedule 04.02.2019
comment
Optional.orElseThrow(...)? Логики много не напишешь...   -  person Turing85    schedule 04.02.2019
comment
Почему вы так ограничены в возвращении Optional в первую очередь? Я имею в виду, что поскольку @Retryable работает с генерируемыми исключениями, вы вполне можете создать свое собственное исключение, генерировать его, если значение не получено, а затем повторить операцию. Таким образом, вы сэкономите время на написании собственной аннотации и тому подобного.   -  person akortex91    schedule 04.02.2019
comment
выбрасывание исключений имеет высокую стоимость производительности, например, см. здесь .com/questions/299068/how-slow-are-java-exceptions   -  person orirab    schedule 04.02.2019
comment
это не преждевременная оптимизация, вы все равно не должны использовать исключения без необходимости   -  person orirab    schedule 04.02.2019


Ответы (4)


Я использовал 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
person Cristian Rodriguez    schedule 15.08.2019
comment
Я немного посмотрел на структуру (очень мило), но не нашел способа реализовать то, что я просил. Насколько я понимаю, ваш код будет означать, что если мой метод вернет пустой вариант с первой попытки, он вообще не будет повторять попытку. Если он пуст, я хочу повторить попытку, пока он не станет пустым, или пока он не повторит попытку 3 раза. - person orirab; 18.08.2019
comment
Привет @orirab. Я обновил ответ проверенным примером. - person Cristian Rodriguez; 20.08.2019
comment
Попробую, выглядит очень красиво. Если это сработает, я, вероятно, приму ваш ответ. Тай! - person orirab; 21.08.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, только что созданное один раз.

person Gary Russell    schedule 04.02.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();
}
person orirab    schedule 04.02.2019

Просто выбросьте исключение, если ваш результат нежелателен

person Hoàng Vinh Quang    schedule 31.03.2020
comment
Этот ответ кажется таким же полезным, как и вы должны улучшить свой ответ. Хотя может я немного преувеличиваю... - person Yunnosch; 31.03.2020