Java:如何在没有复制粘贴代码的情况下处理重试?

时间:2012-03-02 20:23:20

标签: java exception copy-paste

我有多个案例需要处理数据库和网络操作的重试。无论我到哪里,我都有以下类型的代码:

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

问题是这个代码模式在我的类中被复制粘贴。这真的很糟糕。我无法弄清楚如何摆脱这种for-break-catch复制粘贴模式,因为我通常会得到不同的异常来处理,我想记录我失败的数据(通常也是不同的方式)。

有没有一种方法可以避免Java 7中的这种复制粘贴?

编辑:我确实使用guice进行依赖注入。我确实检查了异常。可能有多个变量而不是一个数据,它们都是不同的类型。

Edit2 :AOP方法对我来说是最有希望的。

7 个答案:

答案 0 :(得分:5)

副手,我可以想到两种不同的方法:

如果可以声明性地表达异常处理中的差异,则可以使用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);
                }
            }
        }
    }
}

命令模式的问题是方法参数。您被限制为单个参数,并且对于调用者而言,泛型相当不实用。此外,它不会使用已检查的异常。从好的方面来说,没有花哨的AOP东西: - )

答案 1 :(得分:4)

如前所述,AOP和Java注释是一个不错的选择。我建议使用jcabi-aspects中的读取机制:

@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

答案 2 :(得分:3)

我在下面实现了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类支持异常类参数。

答案 3 :(得分:2)

让您的doSomething实现一个界面,例如Runable并创建一个包含上述代码的方法,其中doSomething替换为interface.run(data)

答案 4 :(得分:1)

看看:this retry utility

此方法适用于大多数用例:

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

使用此实用程序可以通过更少的代码轻松实现方面。

答案 5 :(得分:0)

扩展已经讨论过的方法,这样的事情怎么样(这个上网本上没有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);
        }
    }
}

答案 6 :(得分:0)

我想补充一点。大多数例外(99.999%)表示您的代码或环境存在一些需要管理员注意的问题。如果您的代码无法连接到数据库,那么它可能是一个配置错误的环境,没有必要重试它,只是为了发现它没有在第3,第4或第5时间工作。如果由于此人没有提供有效的信用卡号码而引发例外情况,则重试不会神奇地填写信用卡号。

远程值得重试的唯一情况是系统非常紧张并且事情超时,但在这种情况下,重试逻辑可能会导致更多的压力而不是更少(每次事务3次重试时为3次)。但这就是系统要做的事情,以满足需求(参见阿波罗登陆器任务故事)。当系统被要求做的比它更多时,它开始丢弃作业,超时是系统紧张(或写得不好)的信号。如果你只是增加了系统的容量(增加更多内存,更大的服务器,更多的服务器,更好的算法,扩展它!),你会处于更好的状态。

另一种情况是,如果你使用乐观锁定,你可以以某种方式恢复和自动合并一个对象的两个版本。虽然我之前已经看过这种方法,但是我可以对这种方法提出警告,但是可以在100%的时间内完成合并而不会发生冲突的简单对象。

大多数异常逻辑应该在适当的级别捕获(非常重要),确保您的系统处于良好的一致状态(即回滚事务,关闭文件等),记录它,通知用户它没有#t; t工作

但我会幽默这个想法并试图给出一个好的框架(因为它像填字游戏一样有趣)。

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