我有多个案例需要处理数据库和网络操作的重试。无论我到哪里,我都有以下类型的代码:
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方法对我来说是最有希望的。
答案 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)
此方法适用于大多数用例:
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();
}
}
}
}