2012-03-02 8 views
8

私は、DBとネットワークの操作のためにリトリーを処理しなければならない場合、複数のケースがあります。どこでも私はそれ、私は次のコードの種類を持っているかの操作を行います。Java:コピー・ペースト・コードなしで再試行する方法は?

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を使用します。私は例外をチェックしています。 1つのデータではなく複数の変数があり、それらはすべて異なるタイプです。

Edit2:AOPのアプローチは、私にとって最も有望なものです。アプローチを拡張interface.run(data)

+0

DBアクセスコードがすべての場合、なぜ例外が異なるのはなぜですか?あなたはいくつかの例を挙げることができますか? – DNA

+0

私はちょうど質問を編集しましたが、論理的にはDBとネットワーキングがあり、操作によってはDBだけでも異なる例外で失敗します。また、doSomethingUseful()は、私が同じように扱いたいと思う例外をスローするかもしれません。 – Artem

+1

の例外はチェックされているかチェックされていませんか? – meriton

答えて

5

、私は2つの異なるアプローチを考えることができます:exceptiの違い場合

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の面白いものはありません:-)

+0

私はguiceを使用します、そこにそれを行う良い方法はありますか? – Artem

+0

私はGuiceを知らないが、google検索でhttp://code.google.com/p/google-guice/wiki/AOPが得られた – meriton

2

doSomethingで、例えば、インタフェースを実装し、あなたのdoSomethingくださいRunableと上記のコードを含むメソッドを作成するには、どのようにこのような何か(このネットブックにはIDE、については、すでにdiscusssed擬似コードとしてに関してはこれを...)オフ手

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

これは正しいアイデアだと思いますが、ロギング(おそらく簡単)と例外(恐らく簡単ではないでしょう)を扱うために拡張する必要があります – DNA

+0

私はDNAに同意します。アプローチは正しいですが、例外とカスタムロギングを考慮していません。データの型も常に異なっています。 – Artem

+0

ロギングもインターフェイスに委譲できます。したがって、実際の例外の区別ができます。私は悪魔が詳細にあることを知っています。 – stryba

0

に置き換え

+0

このアプローチは素晴らしいですが、チェックされた例外ではうまくいきません。 – Artem

+0

実際には、署名で基本チェック例外を宣言できます。 – Artem

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

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

以下は使用例です。再試行するコードは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); 
     }}); 
} 

あなたは例外の特定のタイプが発生した場合にのみ再試行(と例外の他のすべてのタイプに失敗)したい場合はRetryLogicクラスは、例外クラスのパラメータをサポートしています。

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) 

あなたはeassilyさえ少ないコードでこれを行うには、このユーティリティを使用して側面を実装することができます。

0

私が追加したいことが1つあります。ほとんどの例外(99.999%)は、管理者の注意が必要なコードや環境に非常に間違いがあることを意味します。あなたのコードがデータベースに接続できないのであれば、環境設定が間違っている可能性があります。それを試してみると、3日目、4日目、5日目のどちらでも動作しないことがわかります。その人が有効なクレジットカード番号を与えなかったために例外を投げている場合、再試行では魔法のようにクレジットカード番号を記入することはありません。

リトライする価値があるのは、システムに大変な負荷がかかり、状況がタイムアウトした場合ですが、この場合、リトライロジックは、トランザクションごとに3回のリトライ(3回のリトライ回数が3回になります)しかし、これはシステムが需要を取り戻すためのものです(アポロ着陸機のミッション・ストーリーを参照)。システムがそれ以上の処理を要求されると、ジョブの廃棄が開始され、タイムアウトはシステムにひどい(または不十分に書かれた)信号である。システムの容量を増やしただけでは、はるかに良い状況になります(ラムを増やし、サーバーを増やし、サーバーを増やし、アルゴリズムを改善し、規模を拡大してください)。

オプティミスティックロックを使用していて、何らかの形でオブジェクトの2つのバージョンをリカバリして自動マージすることができます。私はこれを前に見てきましたが、私はこのアプローチに注意しましたが、コンフリクトなしにマージすることができる単純なオブジェクトに対しては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

私はあなたに例外をキャッチする正しい場所についてあなたに同意する。私はこれが最も重要な部分であることに同意します。しかし、私はあなたが書いている各フレームワークメソッドのコードの上に "try-catch-catch"をコピー貼り付けることについて話していました。 – Artem

+0

Err。よく分からない。上に示した方法では、コピーされる必要のあるtry-catchロジックはありません。クライアントコードでは、私が含まれているtry-catchは、その呼び出しに対して特別なリトライロジックをバイパスしたいいくつかの例外があるかもしれないことを示すことです。私が含むtry-catchは純粋にオプションです。他のフレームワークが追加しなかった特別な機能。 – chubbsondubs

関連する問題