Java: как обрабатывать повторы без копирования-вставки кода? - PullRequest
8 голосов
/ 03 марта 2012

У меня есть несколько случаев, когда мне приходится иметь дело с повторным рассмотрением для операций с БД и сетью. Везде, где я делаю это, у меня есть следующий тип кода:

    for (int iteration = 1; ; iteration++) {
        try {
            data = doSomethingUseful(data);

            break;
        } catch (SomeException | AndAnotherException e) {
            if (iteration == helper.getNumberOfRetries()) {
                throw e;
            } else {
                errorReporter.reportError("Got following error for data = {}. Continue trying after delay...", data, e);
                utilities.defaultDelayForIteration(iteration);
                handleSpecificCase(data);
            }
        }
    }

Проблема в том, что этот кодовый код вставлен во все мои классы. Что действительно плохо. Я не могу понять, как избавиться от этого шаблона копирования-вставки для прерывания-перехвата, так как я обычно получаю другое исключение для обработки, я хочу регистрировать данные, на которых произошел сбой (обычно также разными способами).

Есть ли хороший способ избежать копирования-вставки в Java 7?

Редактировать: Я использую guice для внедрения зависимостей. Я проверил исключения. Вместо одной информации может быть несколько переменных, и все они разного типа.

Edit2 : подход АОП выглядит для меня наиболее перспективным.

Ответы [ 7 ]

5 голосов
/ 03 марта 2012

Я могу думать о двух разных подходах:

Если различия в обработке исключений могут быть выражены декларативно, вы можете использовать AOP для создания кода обработки исключений вокруг ваших методов. Тогда ваш бизнес-код может выглядеть следующим образом:

@Retry(times = 3, loglevel = LogLevel.INFO)
List<User> getActiveUsers() throws DatabaseException {
    // talk to the database
}

Преимущество состоит в том, что действительно легко добавить поведение повторения в метод, недостатком является сложность составления рекомендации (которую нужно реализовать только один раз. Если вы используете библиотеку внедрения зависимостей, скорее всего, это произойдет. предлагаем метод перехвата поддержки).

Другой подход заключается в использовании шаблона команды:

abstract class Retrieable<I,O> {
    private final LogLevel logLevel;

    protected Retrieable(LogLevel loglevel) {
        this.logLevel = loglevel;
    }

    protected abstract O call(I input);

    // subclasses may override to perform custom logic.
    protected void handle(RuntimeException e) {
        // log the exception. 
    }

    public O execute(I input) {
        for (int iteration = 1; ; iteration++) {
            try {
                return call(input);
            } catch (RuntimeException e) {
                if (iteration == helper.getNumberOfRetries()) {
                    throw e;
                } else {
                    handle();
                    utilities.defaultDelayForIteration(iteration);
                }
            }
        }
    }
}

Проблема с шаблоном команды - аргументы метода. Вы ограничены одним параметром, и общие параметры довольно громоздки для вызывающего. Кроме того, он не будет работать с проверенными исключениями. С другой стороны, нет ничего интересного в АОП: -)

4 голосов
/ 03 февраля 2013

Как уже предлагалось, аннотации AOP и Java являются хорошим вариантом.Я бы порекомендовал использовать готовый механизм из jcabi-aspect :

@RetryOnFailure(attempts = 2, delay = 10, verbose = false)
public String load(URL url) {
  return url.openConnection().getContent();
}

Читайте также этот блог: http://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html

3 голосов
/ 16 мая 2013

Ниже я реализовал класс RetryLogic, который предоставляет многократно используемую логику повторения и поддерживает параметры, поскольку повторяемый код находится в переданном делегате.

/**
 * Generic retry logic. Delegate must throw the specified exception type to trigger the retry logic.
 */
public class RetryLogic<T>
{
    public static interface Delegate<T>
    {
        T call() throws Exception;
    }

    private int maxAttempts;
    private int retryWaitSeconds;
    @SuppressWarnings("rawtypes")
    private Class retryExceptionType;

    public RetryLogic(int maxAttempts, int retryWaitSeconds, @SuppressWarnings("rawtypes") Class retryExceptionType)
    {
        this.maxAttempts = maxAttempts;
        this.retryWaitSeconds = retryWaitSeconds;
        this.retryExceptionType = retryExceptionType;
    }

    public T getResult(Delegate<T> caller) throws Exception {
        T result = null;
        int remainingAttempts = maxAttempts;
        do {
            try {
                result = caller.call();
            } catch (Exception e){
                if (e.getClass().equals(retryExceptionType))
                {
                    if (--remainingAttempts == 0)
                    {
                        throw new Exception("Retries exausted.");
                    }
                    else
                    {
                        try {
    Thread.sleep((1000*retryWaitSeconds));
                        } catch (InterruptedException ie) {
                        }
                    }
                }
                else
                {
                    throw e;
                }
            }
        } while  (result == null && remainingAttempts > 0);
        return result;
    }
}

Ниже приведен пример использования. Код для повторения находится в методе вызова.

private MyResultType getDataWithRetry(final String parameter) throws Exception {
    return new RetryLogic<MyResultType>(5, 15, Exception.class).getResult(new RetryLogic.Delegate<MyResultType> () {
        public MyResultType call() throws Exception {
            return  dataLayer.getData(parameter);
        }});
}

В случае, если вы хотите повторить попытку только при возникновении определенного типа исключения (и не во всех остальных типах исключений), класс RetryLogic поддерживает параметр класса исключения.

2 голосов
/ 03 марта 2012

Заставьте ваш doSomething реализовать интерфейс, например, Runable и создайте метод, содержащий указанный выше код, с заменой doSomething на interface.run(data)

1 голос
/ 11 июня 2014

взгляните на: эта утилита повторных попыток

этот метод должен работать для большинства случаев использования:

public static <T> T executeWithRetry(final Callable<T> what, final int nrImmediateRetries,
            final int nrTotalRetries, final int retryWaitMillis, final int timeoutMillis)

вы можете легко реализовать аспект, используя эту утилиту, чтобы сделать это с еще меньшим количеством кода.

0 голосов
/ 11 июня 2014

Я хотел бы добавить одну вещь.Большинство исключений (99,999%) означают, что с вашим кодом или средой что-то не так, что требует внимания администратора.Если ваш код не может подключиться к базе данных, возможно, это неправильно сконфигурированная среда, нет смысла пытаться повторить его, просто чтобы узнать, что он не работает 3-й, 4-й или 5-й раз.Если вы выбрасываете исключение из-за того, что человек не указал действительный номер кредитной карты, повторная попытка не будет волшебным образом заполнять номер кредитной карты.

Единственные ситуации, которые стоит повторить удаленно, это когдасистема чрезвычайно напряжена, и время истекает, но в этой ситуации логика повторных попыток, вероятно, вызовет большую нагрузку, чем меньшую (3 раза за 3 попытки на каждую транзакцию).Но это то, что системы делают, чтобы снизить спрос (см. Историю миссии Apollo Lander).Когда систему просят сделать больше, чем она может, она начинает сбрасывать задания, и тайм-ауты являются сигналом того, что система перегружена (или плохо написана).Вы бы оказались в гораздо лучшем положении, если бы просто увеличили емкость своей системы (добавьте больше оперативной памяти, увеличьте количество серверов, добавьте больше серверов, улучшите алгоритмы, масштабируйте ее!).

Другая ситуация будет, если выВы используете оптимистическую блокировку, и вы можете как-то восстановить и автоматически объединить две версии объекта.Хотя я видел это раньше, я бы предостерег этот подход, но это можно сделать для простых объектов, которые можно объединять без конфликтов в 100% случаев.

Большинство логик исключений должно быть перехвачено на соответствующем уровне (очень важно), убедитесь, что ваша система находится в хорошем согласованном состоянии (например, транзакции отката, закрытие файлов и т. д.), зарегистрируйте ее, сообщите пользователю, что она не работает.попробуйте дать хорошую основу (ну, потому что это весело, как кроссворд).

// client code - what you write a lot
public class SomeDao {
    public SomeReturn saveObject( final SomeObject obj ) throws RetryException {
        Retry<SomeReturn> retry = new Retry<SomeReturn>() {
            public SomeReturn execute() throws Exception {
               try {
                  // doSomething
                  return someReturn;
               } catch( SomeExpectedBadExceptionNotWorthRetrying ex ) {
                  throw new NoRetryException( ex ); // optional exception block
               }
            }
        }
        return retry.run();
    }
}

// framework - what you write once
public abstract class Retry<T> {

    public static final int MAX_RETRIES = 3;

    private int tries = 0;

    public T execute() throws Exception;

    public T run() throws RetryException {
        try {
           return execute();
        } catch( NoRetryException ex ) {
           throw ex;
        } catch( Exception ex ) {
           tries++;
           if( MAX_RETRIES == tries ) {
              throw new RetryException("Maximum retries exceeded", ex );
           } else {
              return run();
           }
        }
    }
}
0 голосов
/ 03 марта 2012

Расширяя уже обсуждаемый подход, как насчет чего-то вроде этого (на этом нетбуке нет IDE, так что расценивайте это как псевдокод ...)

// generics left as an exercise for the reader...
public Object doWithRetry(Retryable r){
for (int iteration = 1; ; iteration++) {
    try {
        return r.doSomethingUseful(data);
    } catch (Exception e) {
        if (r.isRetryException(e)) {
           if(r.tooManyRetries(i){
            throw e;
           }
        } else {
           r.handleOtherException(e);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...