您如何实施重试捕获?


203

Try-catch旨在帮助处理异常。这意味着它将以某种方式帮助我们的系统更强大:尝试从意外事件中恢复。

我们怀疑执行和指令(发送消息)时可能会发生某些事情,因此将其包含在try中。如果发生了几乎不可预料的事情,我们可以采取一些措施:编写渔获物。我不认为我们打电话来只是记录异常。我认为catch块旨在为我们提供从错误中恢复的机会。

现在,假设我们从错误中恢复了,因为我们可以修复错误所在。重试可能是非常好的:

try{ some_instruction(); }
catch (NearlyUnexpectedException e){
   fix_the_problem();
   retry;
}

这将很快陷入永恒的循环,但是假设fix_the_problem返回true,然后我们重试。鉴于Java中没有这样的东西,您将如何解决此问题?解决该问题的最佳设计代码是什么?

鉴于我已经知道我所要的东西并没有被Java直接支持,所以这就像一个哲学问题。


5
那是什么例外?
Bhesh Gurung

23
我喜欢你例外的名字。;)
Rohit Jain 2012年

实际上,可以从中恢复的例外并不多。我承认我的最初动机并不是真正的例外,但是避免发生这种情况的方法几乎永远不会发生:我尝试remove()java.util.Queue,哪个队列以及InvalidElementException何时队列为空。我没有询问它是否为空,而是在try-catch中对操作进行了处理(在并发情况下,即使使用先前的if,强制执行)也是如此。在这种情况下,catch我将在块中要求用更多元素填充队列,然后重试。瞧
安德列斯·法里亚斯

1
我可以看到执行此操作的通常方法是用于数据库访问,如果连接重新连接失败,如果连接失败,则抛出主要异常,否则重试该调用。如前所述,我们可以在循环中执行此操作,并在底部检查if(error <> 0),然后返回,否则会中断;
Theresa Forster 2014年

Answers:


304

您需要将您try-catchwhile循环包含在这样的循环中:-

int count = 0;
int maxTries = 3;
while(true) {
    try {
        // Some Code
        // break out of loop, or return, on success
    } catch (SomeException e) {
        // handle exception
        if (++count == maxTries) throw e;
    }
}

我已采取countmaxTries避免陷入无限循环,以防您的中不断发生异常try block


3
我起初以为是这样的,没有maxTries。感谢你的回答!
Andres Farias 2012年

6
@AndresFarias ..是的,此答案中最重要的一点是包括一个maxTries。否则,infinite loop如果用户连续输入错误,它将遇到错误,因此不会退出。不客气。:)
Rohit Jain 2012年

谢谢你-它使我不必编写一些非常粗糙的代码!
David Holiday

2
是否可以在此处的catch中添加Thread.sleep()函数。因为在某些情况下,例如等待Selenium库中的页面响应就变得至关重要。谢谢。
Suat Atan PhD

2
很棒!对于初学者:如果出现正无限循环,请检查是否添加了“ break;”。最后在“ try”块中。
Krzysztof Walczewski

59

强制性的“企业”解决方案:

public abstract class Operation {
    abstract public void doIt();
    public void handleException(Exception cause) {
        //default impl: do nothing, log the exception, etc.
    }
}

public class OperationHelper {
    public static void doWithRetry(int maxAttempts, Operation operation) {
        for (int count = 0; count < maxAttempts; count++) {
            try {
                operation.doIt();
                count = maxAttempts; //don't retry
            } catch (Exception e) {
                operation.handleException(e);
            }
        }
    }
}

并致电:

OperationHelper.doWithRetry(5, new Operation() {
    @Override public void doIt() {
        //do some stuff
    }
    @Override public void handleException(Exception cause) {
        //recover from the Exception
    }
});

6
如果最后一次重试失败,则应该重新引发异常,如在其他给出的答案中所做的那样。
cvacca 2014年

35

像往常一样,最佳设计取决于特定情况。通常,虽然我写类似:

for (int retries = 0;; retries++) {
    try {
        return doSomething();
    } catch (SomeException e) {
        if (retries < 6) {
            continue;
        } else {
            throw e;
        }
    }
}

等一下,为什么在for循环声明中没有条件,如:for(int retries = 0; retries <6; retries ++)?
Didier A.

8
因为我只想进行最后一次尝试,因此catch块需要该条件,因此该条件为for冗余。
meriton 2014年

1
我认为continue那里不需要。而且您可以简单地翻转if条件。
Koray Tugay

19

尽管try/catch进入while是众所周知的好策略,但我还是建议您递归调用:

void retry(int i, int limit) {
    try {

    } catch (SomeException e) {
        // handle exception
        if (i >= limit) {
            throw e;  // variant: wrap the exception, e.g. throw new RuntimeException(e);
        }
        retry(i++, limit);
    }
}

41
对于此用例,递归比循环好吗?
2012年

7
堆栈跟踪可能看起来有点奇怪,因为它没有limit计数递归的方法吗?与循环版本相反,后者将以“原始”级别出现……
Clockwork-Muse 2012年

7
当然在纸上看起来很优雅,但是我不确定递归是否是正确的方法。
Thomas

3
我不明白为什么还要在这里递归。无论如何,我认为它可以简化为:void retry(int times) { (...) if (times==0) throw w; retry(times--);
sinuhepop

8
用递归代替单纯的迭代是很差的做法。当您要推送和弹出某些数据时,可以使用递归。
洛恩侯爵,2015年

19

您通过Failsafe处理的确切情况:

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(NearlyUnexpectedException.class);

Failsafe.with(retryPolicy)
  .onRetry((r, f) -> fix_the_problem())
  .run(() -> some_instruction());

很简单


5
非常好的图书馆。
马克西姆(Maksim)

对于那些想知道的人,您将在gradle依赖项中需要它-编译'net.jodah:failsafe:1.1.0'–
Shreyas

18

您可以从jcabi-aspects使用AOP和Java注释(我是开发人员):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
  return url.openConnection().getContent();
}

您也可以使用@Loggable@LogException注释。


哇 !听起来很花哨!:)
Alind Billore 2015年

应该是最佳答案。
Mohamed Taher Alrefaie '16

2
尝试失败时,是否有办法“修复”错误(某些采用方法可以修复下一次尝试)?看到问题:fix_the_problem();在捕获区中
前进

鉴于未解决的问题数量和无法解决已确认错误的时间,我不会依赖此库。
迈克尔·里斯

6

这些答案大多数都基本相同。我的也是,但这是我喜欢的形式

boolean completed = false;
Throwable lastException = null;
for (int tryCount=0; tryCount < config.MAX_SOME_OPERATION_RETRIES; tryCount++)
{
    try {
        completed = some_operation();
        break;
    }
    catch (UnlikelyException e) {
        lastException = e;
        fix_the_problem();
    }
}
if (!completed) {
    reportError(lastException);
}

一个缺点是您fix_the_problem在最后一次尝试后也要打电话。这可能是一项昂贵的操作,并且可能会浪费一些时间。
约阿希姆·绍尔

2
@JoachimSauer是的。您可以if (tryCount < max) fix()-但这是一般方法的格式;具体取决于具体情况。我一直在看基于番石榴的Retryer
斯蒂芬·P

4

Spring AOP和基于注释的解决方案:

用法(@RetryOperation是我们对作业的自定义注释):

@RetryOperation(retryCount = 1, waitSeconds = 10)
boolean someMethod() throws Exception {
}

我们需要两件事来完成此任务:1.注释接口,和2. spring方面。这是实现这些的一种方法:

注释界面:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOperation {
    int retryCount();
    int waitSeconds();
}

春季方面:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Aspect @Component 
public class RetryAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(RetryAspect.class);

    @Around(value = "@annotation(RetryOperation)")
    public Object retryOperation(ProceedingJoinPoint joinPoint) throws Throwable {

        Object response = null;
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RetryOperation annotation = method.getAnnotation(RetryOperation.class);
        int retryCount = annotation.retryCount();
        int waitSeconds = annotation.waitSeconds();
        boolean successful = false;

        do {
            try {
                response = joinPoint.proceed();
                successful = true;
            } catch (Exception ex) {
                LOGGER.info("Operation failed, retries remaining: {}", retryCount);
                retryCount--;
                if (retryCount < 0) {
                    throw ex;
                }
                if (waitSeconds > 0) {
                    LOGGER.info("Waiting for {} second(s) before next retry", waitSeconds);
                    Thread.sleep(waitSeconds * 1000l);
                }
            }
        } while (!successful);

        return response;
    }
}

3

使用while带有局部status标志的循环。将标志初始化为,falsetrue在操作成功时将其设置为,例如:

  boolean success  = false;
  while(!success){
     try{ 
         some_instruction(); 
         success = true;
     } catch (NearlyUnexpectedException e){
       fix_the_problem();
     }
  }

这将一直重试直到成功。

如果您只想重试一定次数,则也可以使用计数器:

  boolean success  = false;
  int count = 0, MAX_TRIES = 10;
  while(!success && count++ < MAX_TRIES){
     try{ 
         some_instruction(); 
         success = true;
     } catch (NearlyUnexpectedException e){
       fix_the_problem();
     }
  }
  if(!success){
    //It wasn't successful after 10 retries
  }

如果不成功,它将最多尝试10次,然后如果之前成功将退出。


!success您不必花一会儿时间,而可以在成功成真时休息一下。
罗希特·贾因

1
@RohitJain:对我来说看起来更干净。
Yogendra Singh '11

@YogendraSingh ..奇怪。因为您没有success在自己的任何地方修改自己catch。因此,在每次运行时都进行检查似乎很多余catch
罗希特·贾因

@RohitJain:Catch只是在纠正数据。它将返回并再次运行该语句。如果成功,它将修改success。试试看。
Yogendra Singh 2012年

3

这是一个老问题,但是解决方案仍然有意义。这是我在Java 8中的通用解决方案,没有使用任何第三方库:

public interface RetryConsumer<T> {
    T evaluate() throws Throwable;
}
public interface RetryPredicate<T> {
    boolean shouldRetry(T t);
}
public class RetryOperation<T> {
    private RetryConsumer<T> retryConsumer;
    private int noOfRetry;
    private int delayInterval;
    private TimeUnit timeUnit;
    private RetryPredicate<T> retryPredicate;
    private List<Class<? extends Throwable>> exceptionList;

    public static class OperationBuilder<T> {
        private RetryConsumer<T> iRetryConsumer;
        private int iNoOfRetry;
        private int iDelayInterval;
        private TimeUnit iTimeUnit;
        private RetryPredicate<T> iRetryPredicate;
        private Class<? extends Throwable>[] exceptionClasses;

        private OperationBuilder() {
        }

        public OperationBuilder<T> retryConsumer(final RetryConsumer<T> retryConsumer) {
            this.iRetryConsumer = retryConsumer;
            return this;
        }

        public OperationBuilder<T> noOfRetry(final int noOfRetry) {
            this.iNoOfRetry = noOfRetry;
            return this;
        }

        public OperationBuilder<T> delayInterval(final int delayInterval, final TimeUnit timeUnit) {
            this.iDelayInterval = delayInterval;
            this.iTimeUnit = timeUnit;
            return this;
        }

        public OperationBuilder<T> retryPredicate(final RetryPredicate<T> retryPredicate) {
            this.iRetryPredicate = retryPredicate;
            return this;
        }

        @SafeVarargs
        public final OperationBuilder<T> retryOn(final Class<? extends Throwable>... exceptionClasses) {
            this.exceptionClasses = exceptionClasses;
            return this;
        }

        public RetryOperation<T> build() {
            if (Objects.isNull(iRetryConsumer)) {
                throw new RuntimeException("'#retryConsumer:RetryConsumer<T>' not set");
            }

            List<Class<? extends Throwable>> exceptionList = new ArrayList<>();
            if (Objects.nonNull(exceptionClasses) && exceptionClasses.length > 0) {
                exceptionList = Arrays.asList(exceptionClasses);
            }
            iNoOfRetry = iNoOfRetry == 0 ? 1 : 0;
            iTimeUnit = Objects.isNull(iTimeUnit) ? TimeUnit.MILLISECONDS : iTimeUnit;
            return new RetryOperation<>(iRetryConsumer, iNoOfRetry, iDelayInterval, iTimeUnit, iRetryPredicate, exceptionList);
        }
    }

    public static <T> OperationBuilder<T> newBuilder() {
        return new OperationBuilder<>();
    }

    private RetryOperation(RetryConsumer<T> retryConsumer, int noOfRetry, int delayInterval, TimeUnit timeUnit,
                           RetryPredicate<T> retryPredicate, List<Class<? extends Throwable>> exceptionList) {
        this.retryConsumer = retryConsumer;
        this.noOfRetry = noOfRetry;
        this.delayInterval = delayInterval;
        this.timeUnit = timeUnit;
        this.retryPredicate = retryPredicate;
        this.exceptionList = exceptionList;
    }

    public T retry() throws Throwable {
        T result = null;
        int retries = 0;
        while (retries < noOfRetry) {
            try {
                result = retryConsumer.evaluate();
                if (Objects.nonNull(retryPredicate)) {
                    boolean shouldItRetry = retryPredicate.shouldRetry(result);
                    if (shouldItRetry) {
                        retries = increaseRetryCountAndSleep(retries);
                    } else {
                        return result;
                    }
                } else {
                    // no retry condition defined, no exception thrown. This is the desired result.
                    return result;
                }
            } catch (Throwable e) {
                retries = handleException(retries, e);
            }
        }
        return result;
    }

    private int handleException(int retries, Throwable e) throws Throwable {
        if (exceptionList.contains(e.getClass()) || (exceptionList.isEmpty())) {
            // exception is excepted, continue retry.
            retries = increaseRetryCountAndSleep(retries);
            if (retries == noOfRetry) {
                // evaluation is throwing exception, no more retry left. Throw it.
                throw e;
            }
        } else {
            // unexpected exception, no retry required. Throw it.
            throw e;
        }
        return retries;
    }

    private int increaseRetryCountAndSleep(int retries) {
        retries++;
        if (retries < noOfRetry && delayInterval > 0) {
            try {
                timeUnit.sleep(delayInterval);
            } catch (InterruptedException ignore) {
                Thread.currentThread().interrupt();
            }
        }
        return retries;
    }
}

让我们有一个测试用例,例如:

@Test
public void withPredicateAndException() {
    AtomicInteger integer = new AtomicInteger();
    try {
        Integer result = RetryOperation.<Integer>newBuilder()
                .retryConsumer(() -> {
                    int i = integer.incrementAndGet();
                    if (i % 2 == 1) {
                        throw new NumberFormatException("Very odd exception");
                    } else {
                        return i;
                    }
                })
                .noOfRetry(10)
                .delayInterval(10, TimeUnit.MILLISECONDS)
                .retryPredicate(value -> value <= 6)
                .retryOn(NumberFormatException.class, EOFException.class)
                .build()
                .retry();
        Assert.assertEquals(8, result.intValue());
    } catch (Throwable throwable) {
        Assert.fail();
    }
}

好主意,一个建造者!
HankTheTank '19

2

解决此问题的一种简单方法是将try / catch包装在while循环中并保持计数。这样,您可以通过在维护失败日志的同时检查其他变量的计数来防止无限循环。它不是最精致的解决方案,但可以使用。



1

在有用的情况下,可以考虑几个其他选项(将它们放在一起(用stopfile代替重试,睡眠,继续更大的循环))可能都有用。

 bigLoop:
 while(!stopFileExists()) {
    try {
      // do work
      break;
    }
    catch (ExpectedExceptionType e) {

       // could sleep in here, too.

       // another option would be to "restart" some bigger loop, like
       continue bigLoop;
    }
    // ... more work
}

不赞成投票的人请留下评论,为什么,谢谢!
rogerdpack '16

1
这是无知的无奈之举,并没有提出理由。
xploreraj

睡觉没有显而易见的,因为while循环不会等待
若昂·皮门特尔·费雷拉

1

您可以使用https://github.com/bnsd55/RetryCatch

例:

RetryCatch retryCatchSyncRunnable = new RetryCatch();
        retryCatchSyncRunnable
                // For infinite retry times, just remove this row
                .retryCount(3)
                // For retrying on all exceptions, just remove this row
                .retryOn(ArithmeticException.class, IndexOutOfBoundsException.class)
                .onSuccess(() -> System.out.println("Success, There is no result because this is a runnable."))
                .onRetry((retryCount, e) -> System.out.println("Retry count: " + retryCount + ", Exception message: " + e.getMessage()))
                .onFailure(e -> System.out.println("Failure: Exception message: " + e.getMessage()))
                .run(new ExampleRunnable());

而是new ExampleRunnable()可以传递自己的匿名函数。


1

如果不是所有例外都需要重试,则只有一部分例外。如果必须至少尝试一次,则可以使用以下替代方法:

void runWithRetry(Runnable runnable, Class<Exception> exClass, int maxRetries) {
        Exception err = null;
        do {
            maxRetries--;
            try {
                runnable.run();
                err = null;
            } catch (Exception e) {
                if(exClass.isAssignableFrom(e.getClass())){
                    err = e;
                }else {
                    throw e;
                }
            }
        } while (err != null && maxRetries > 0);

        if (err != null) {
            throw err;
        }
    }

用法:

    runWithRetry(() -> {
       // do something
    }, TimeoutException.class, 5)

0

Try-Catch所做的只是允许您的程序正常失败。在catch语句中,通常会尝试记录错误,并可能在需要时回滚更改。

bool finished = false;

while(finished == false)
{
    try
    {
        //your code here
        finished = true
    }
    catch(exception ex)
    {
        log.error("there was an error, ex");
    }
}

你的意思是反对(!finished)
山姆,我是说恢复莫妮卡(Monica)2012年

1
@RohitJain看起来太像了while(finished)。我更喜欢使用更详细的版本。
山姆,我是说要恢复莫妮卡(Monica)

3
到底是while(!finished)什么样的while (finished)
罗希特·贾因

@Rohit因为只有一个字符不同。他们都被归纳为同一件事。在C#中,我使用String扩展方法IsPopulated(),该方法只是返回!IsNullOrEmpty()以确保所有开发人员都能理解我的意图。
迈克尔·布莱克本

0

我知道这里已经有很多类似的答案,我的也没有太大不同,但是我还是会发布它,因为它处理的是特定案例/问题。

当处理facebook Graph APIin时,PHP您有时会出错,但是立即重试同一件事将获得肯定的结果(由于各种不可思议的 Internet原因,超出了此问题的范围)。在这种情况下,无需修复任何错误,而只需再次尝试,因为存在某种“ facebook错误”。

创建Facebook会话后立即使用以下代码:

//try more than once because sometimes "facebook error"
$attempt = 3;
while($attempt-- > 0)
{
    // To validate the session:
    try 
    {
        $facebook_session->validate();
        $attempt = 0;
    } 
    catch (Facebook\FacebookRequestException $ex)
    {
        // Session not valid, Graph API returned an exception with the reason.
        if($attempt <= 0){ echo $ex->getMessage(); }
    } 
    catch (\Exception $ex) 
    {
        // Graph API returned info, but it may mismatch the current app or have expired.
        if($attempt <= 0){ echo $ex->getMessage(); }
    }
}

而且,通过将for循环计数减少到零($attempt--),可以很容易地更改将来的尝试次数。


0

以下是我用非常简单的方法解决的方法!

               while (true) {
                    try {
                        /// Statement what may cause an error;
                        break;
                    } catch (Exception e) {

                    }
                }

1
请查看@Rohit Jain答案,该答案更具体,在否定情况下不是无限循环。
Chandra Shekhar

0

我不确定这是否是“专业”方式,我也不完全确定它是否适用于所有情况。

boolean gotError = false;

do {
    try {
        // Code You're Trying
    } catch ( FileNotFoundException ex ) {
        // Exception
        gotError = true;
    }
} while ( gotError = true );


0

这里是Java 8+的可重用且更通用的方法,不需要外部库:

public interface IUnreliable<T extends Exception>
{
    void tryRun ( ) throws T;
}

public static <T extends Exception> void retry (int retryCount, IUnreliable<T> runnable) throws T {
    for (int retries = 0;; retries++) {
        try {
            runnable.tryRun();
            return;
        } catch (Exception e) {
            if (retries < retryCount) {
                continue;
            } else {
                throw e;
            }
        }
    }
}

用法:

@Test
public void demo() throws IOException {
    retry(3, () -> {
        new File("/tmp/test.txt").createNewFile();
    });
}

0

其余解决方案的问题在于,相应的函数连续尝试,而中间没有时间间隔,从而导致堆栈泛滥。

为什么try不只每秒一遍又一遍呢?

这是使用setTimeout和递归函数的解决方案:

(function(){
  try{
    Run(); //tries for the 1st time, but Run() as function is not yet defined
  }
  catch(e){
    (function retry(){
      setTimeout(function(){
        try{
          console.log("trying...");
          Run();
          console.log("success!");
        }
        catch(e){
          retry(); //calls recursively
        }
      }, 1000); //tries every second
    }());
  }
})();



//after 5 seconds, defines Run as a global function
var Run;
setTimeout(function(){
  Run = function(){};
}, 5000);

将函数替换为Run()您想try每秒重新执行的函数或代码。


0

使用springs @ Retryable注解尝试一下,当RuntimeException发生时,以下方法将重试3次

@Retryable(maxAttempts=3,value= {RuntimeException.class},backoff = @Backoff(delay = 500))
public void checkSpringRetry(String str) {
    if(StringUtils.equalsIgnoreCase(str, "R")) {
        LOGGER.info("Inside retry.....!!");
        throw new RuntimeException();
    }
}

0

在代码段下方,执行一些代码段。如果在执行代码片段时遇到任何错误,请睡眠M毫秒,然后重试。参考链接

public void retryAndExecuteErrorProneCode(int noOfTimesToRetry, CodeSnippet codeSnippet, int sleepTimeInMillis)
  throws InterruptedException {

 int currentExecutionCount = 0;
 boolean codeExecuted = false;

 while (currentExecutionCount < noOfTimesToRetry) {
  try {
   codeSnippet.errorProneCode();
   System.out.println("Code executed successfully!!!!");
   codeExecuted = true;
   break;
  } catch (Exception e) {
   // Retry after 100 milliseconds
   TimeUnit.MILLISECONDS.sleep(sleepTimeInMillis);
   System.out.println(e.getMessage());
  } finally {
   currentExecutionCount++;
  }
 }

 if (!codeExecuted)
  throw new RuntimeException("Can't execute the code within given retries : " + noOfTimesToRetry);
}

0

这是我的解决方案,与其他一些可以包装函数的解决方案类似,但是如果成功,则允许您获取函数的返回值。

    /**
     * Wraps a function with retry logic allowing exceptions to be caught and retires made.
     *
     * @param function the function to retry
     * @param maxRetries maximum number of retires before failing
     * @param delay time to wait between each retry
     * @param allowedExceptionTypes exception types where if caught a retry will be performed
     * @param <V> return type of the function
     * @return the value returned by the function if successful
     * @throws Exception Either an unexpected exception from the function or a {@link RuntimeException} if maxRetries is exceeded
     */
    @SafeVarargs
    public static <V> V runWithRetriesAndDelay(Callable<V> function, int maxRetries, Duration delay, Class<? extends Exception>... allowedExceptionTypes) throws Exception {
        final Set<Class<? extends Exception>> exceptions = new HashSet<>(Arrays.asList(allowedExceptionTypes));
        for(int i = 1; i <= maxRetries; i++) {
            try {
                return function.call();
            } catch (Exception e) {
                if(exceptions.contains(e.getClass())){
                    // An exception of an expected type
                    System.out.println("Attempt [" + i + "/" + maxRetries + "] Caught exception [" + e.getClass() + "]");
                    // Pause for the delay time
                    Thread.sleep(delay.toMillis());
                }else {
                    // An unexpected exception type
                    throw e;
                }
            }
        }
        throw new RuntimeException(maxRetries + " retries exceeded");
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.