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

Java:如何在没有复制粘贴代码的情况下处理重试?,java,exception,copy-paste,Java,Exception,Copy Paste,我有多个案件需要对数据库和网络操作进行重审。无论我在哪里做,我都有以下类型的代码: for (int iteration = 1; ; iteration++) { try { data = doSomethingUseful(data); break; } catch (SomeException | AndAnotherException e) { if (iteration =

我有多个案件需要对数据库和网络操作进行重审。无论我在哪里做,我都有以下类型的代码:

    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);
            }
        }
    }
问题是这个代码模式被复制粘贴到我的所有类中。这真的很糟糕。我不知道如何摆脱这种break-catch-copy-paste模式,因为我通常会处理不同的异常,我想记录失败的数据(通常也有不同的方式)

在Java7中有没有避免这种复制粘贴的好方法

编辑:我确实使用guice进行依赖注入。我确实检查过例外情况。可以有多个变量而不是一个数据,并且它们都是不同类型的


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

让你的
doSomething
实现一个接口,例如
可运行的
,并创建一个包含你上面代码的方法,用
doSomething
替换为
接口。运行(数据)
扩展已经讨论过的方法,像这样的东西怎么样(这个上网本上没有IDE,所以把它当作伪代码…)


我可以马上想到两种不同的方法:

如果异常处理中的差异可以用声明方式表示,那么可以使用AOP在方法周围编织异常处理代码。然后,您的业务代码可能如下所示:

@Retry(times = 3, loglevel = LogLevel.INFO)
List<User> getActiveUsers() throws DatabaseException {
    // talk to the database
}
@重试(次数=3,日志级别=loglevel.INFO)
List getActiveUsers()引发DatabaseException{
//与数据库对话
}
优点是很容易将重试行为添加到方法中,缺点是编织建议的复杂性(您只需实现一次。如果您使用的是依赖项注入库,它很可能会提供方法拦截支持)

另一种方法是使用命令模式:

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);
                }
            }
        }
    }
}
抽象类可检索{
私人最终日志级别日志级别;
受保护可检索(日志级别日志级别){
this.logLevel=logLevel;
}
受保护的抽象O调用(I输入);
//子类可以重写以执行自定义逻辑。
受保护的无效句柄(运行时异常e){
//记录异常。
}
公共O执行(I输入){
for(int迭代=1;迭代++){
试一试{
回电(输入);
}捕获(运行时异常e){
if(迭代==helper.getNumberOfRetries()){
投掷e;
}否则{
句柄();
实用程序.defaultDelayForIteration(迭代);
}
}
}
}
}

命令模式的问题在于方法参数。您仅限于一个参数,并且泛型对于调用方来说相当笨拙。此外,它不会与选中的异常一起工作。从好的方面来说,没有华丽的AOP内容:-)

正如前面所建议的,AOP和Java注释是一个不错的选择。我建议从以下位置使用读取机制:


请阅读这篇博文:

我已经实现了下面的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;
    }
}
/**
*通用重试逻辑。委托必须抛出指定的异常类型才能触发重试逻辑。
*/
公共类RetryLogic
{
公共静态接口委托
{
T call()抛出异常;
}
私有int-maxtures;
私有int retryWaitSeconds;
@抑制警告(“原始类型”)
私有类retryExceptionType;
公共RetryLogic(int-maxAttempts、int-retryWaitSeconds、@SuppressWarnings(“rawtypes”)类retryExceptionType)
{
this.maxAttempts=maxAttempts;
this.retryWaitSeconds=retryWaitSeconds;
this.retryExceptionType=retryExceptionType;
}
公共T getResult(委托调用方)引发异常{
T结果=null;
int remainingAttempts=最大尝试次数;
做{
试一试{
结果=caller.call();
}捕获(例外e){
如果(例如getClass()等于(retryExceptionType))
{
如果(--remainingAttempts==0)
{
抛出新异常(“重试次数已被检查”);
}
其他的
{
试一试{
睡眠((1000*retryWaitSeconds));
}捕获(中断异常ie){
}
}
}
其他的
{
投掷e;
}
}
}while(result==null&&remaingattempts>0);
返回结果;
}
}
下面是一个使用示例。要重试的代码在call方法中

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);
        }});
}
private MyResultType getDataWithRetry(最终字符串参数)引发异常{
返回新的RetryLogic(5,15,Exception.class).getResult(新的RetryLogic.Delegate(){
公共MyResultType调用()引发异常{
返回dataLayer.getData(参数);
}});
}
如果您只想在特定类型的异常发生时重试(并在所有其他类型的异常上失败),RetryLogic类支持异常类参数。

请查看:

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

public static <T> T executeWithRetry(final Callable<T> what, final int nrImmediateRetries,
            final int nrTotalRetries, final int retryWaitMillis, final int timeoutMillis)
public static T executeWithRetry(final Callable what,final int nrimediateretries,
最终整数nrTotalRetries、最终整数retryWaitMillis、最终整数timeoutMillis)

您可以使用此实用程序轻松地实现一个方面,只需更少的代码即可实现。

我想补充一点。大多数异常(99.999%)意味着您的代码或环境存在严重问题,需要管理员注意。如果您的代码无法连接到数据库,则可能是错误配置
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);
        }});
}
public static <T> T executeWithRetry(final Callable<T> what, final int nrImmediateRetries,
            final int nrTotalRetries, final int retryWaitMillis, final int timeoutMillis)
// 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();
           }
        }
    }
}