2016-04-23 20 views
0

私はSpring AOPを初めて使用しており、少し実験しています。Spring AOP - 再試行アドバイスを正しく設定する

リトライを設定しようとしています&私のプロジェクトの1つとして、レートリミッターをSpring AOPで設定しようとしています。 ユースケースは次のようになります。 -

  1. TPSが利用可能かどうかを確認します。そうでない場合はThrottledException
  2. ThrottledExceptionがスローされた場合はRetryとなります。

私はに実行しています問題は、次のとおりです。(TPS = 0の場合)、このスロットル&再試行コンボが無限ループに実行されています。つまり、「x」試行後に再試行が中断していません。

私のスロットルインターセプタは、このように(ハイレベル)である:ここでは

@AfterThrowing(pointcut="execution(* com.company.xyz.method())", throwing="exception") 
public Object invoke(JoinPoint jp, ThrottlingException exception) throws Throwable { 
    return RetryingCallable.newRetryingCallable(new Callable<Object>() { 

     @Override 
     public Object call() throws Exception { 
       MethodSignature signature = (MethodSignature) p.getSignature(); 
       Method method = signature.getMethod(); 
       return method.invoke(jp.getThis(), (Object[]) null); 
     } 

    }, retryPolicy).call(); 
} 

RetryingCallableが誰かによって書かれた簡単な実装(内部ライブラリです:

@Before("<pointcut>") 
public void invoke() throws ThrottlingException { 
     if (throttler.isThrottled(throttleKey)) { 
      throw new ThrottlingException("Call Throttled"); 
    } 
} 

マイ再試行インターセプタはこのようなものです私の会社で)RetryAdviceを取り、それを適用します。次のように

マイ関連の春-configがある:

<bean id="retryInterceptor" class="com.company.xyz.RetryInterceptor"> 
    <constructor-arg index="0"><ref bean="retryPolicy"/></constructor-arg> 
</bean> 


<bean id="throttlingInterceptor" class="com.company.xyz.ThrottlingInterceptor"> 
    <constructor-arg><value>throttleKey</value></constructor-arg> 
</bean> 
<context:component-scan base-package="com.company.xyz"> 
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/> 
</context:component-scan> 
<aop:aspectj-autoproxy/> 

問題ここでは、私が見るように、各ThrottlingException新しいRetry Adviceに代わりに影響を与えるに入ってくるという以前のもので適用されていることです。

これを修正する方法についての入力はありますか?

答えて

2

免責事項:私はない春ユーザー午前、ので、私はここに純粋なAspectJのソリューションを提示するつもりです。しかし、Spring AOPでも同じように動作するはずです。変更する必要があるのは、Spring AOP manualで説明されているように、アスペクト優先設定の場合は、@DeclarePresedenceから@Orderへの切り替えのみです。

ドライバアプリケーション:

package de.scrum_master.app; 

public class Application { 
    public static void main(String[] args) { 
     new Application().doSomething(); 
    } 

    public void doSomething() { 
     System.out.println("Doing something"); 
    } 
} 

スロットル例外クラス:

package de.scrum_master.app; 

public class ThrottlingException extends RuntimeException { 
    private static final long serialVersionUID = 1L; 

    public ThrottlingException(String arg0) { 
     super(arg0); 
    } 
} 

スロットリングインターセプタ:私はヘルパーメソッドを作成し、絞り状況をエミュレートするために

isThrottled()は、3例中2例でランダムにtrueを返します。

package de.scrum_master.aspect; 

import java.util.Random; 

import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Before; 

import de.scrum_master.app.ThrottlingException; 

@Aspect 
public class ThrottlingInterceptor { 
    private static final Random RANDOM = new Random(); 

    @Before("execution(* doSomething())") 
    public void invoke(JoinPoint thisJoinPoint) throws ThrottlingException { 
     System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint); 
     if (isThrottled()) { 
      throw new ThrottlingException("call throttled"); 
     } 
    } 

    private boolean isThrottled() { 
     return RANDOM.nextInt(3) > 0; 
    } 
} 

再試行インターセプタ:

AspectJの注釈@DeclarePrecedence("RetryInterceptor, *")はこのインターセプタは、他のものの前に実行されることを述べていることに注意してください。両方のインターセプタクラスでアノテーションを@Orderアノテーションに置き換えてください。さもなければ、@Aroundアドバイスは、スロットルインターセプターによってスローされた例外をキャッチできません。

また、このインターセプタはリトライロジックを実装するためにリフレクションを必要としないこと、リトライループ内でjoinpointを直接使用してthisJoinPoint.proceed()を再試行することにも言及する価値があります。これは、さまざまな種類の再試行動作を実装するヘルパー・メソッドまたはヘルパー・クラスに簡単に組み込むことができます。 Callableではなく、ProceedingJoinPointをパラメータとして使用してください。

package de.scrum_master.aspect; 

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.DeclarePrecedence; 

import de.scrum_master.app.ThrottlingException; 

@Aspect 
@DeclarePrecedence("RetryInterceptor, *") 
public class RetryInterceptor { 
    private static int MAX_TRIES = 5; 
    private static int WAIT_MILLIS_BETWEEN_TRIES = 1000; 

    @Around("execution(* doSomething())") 
    public Object invoke(ProceedingJoinPoint thisJoinPoint) throws Throwable { 
     System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint); 
     ThrottlingException throttlingException = null; 
     for (int i = 1; i <= MAX_TRIES; i++) { 
      try { 
       return thisJoinPoint.proceed(); 
      } 
      catch (ThrottlingException e) { 
       throttlingException = e; 
       System.out.println(" Throttled during try #" + i); 
       Thread.sleep(WAIT_MILLIS_BETWEEN_TRIES); 
      } 
     } 
     throw throttlingException; 
    } 
} 

リトライ成功のためのコンソールログ:失敗した再試行のための

RetryInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #1 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #2 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
Doing something 

コンソールログ:

RetryInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #1 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #2 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #3 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #4 
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething()) 
    Throttled during try #5 
Exception in thread "main" de.scrum_master.app.ThrottlingException: call throttled 
    at de.scrum_master.aspect.ThrottlingInterceptor.invoke(ThrottlingInterceptor.aj:19) 
    at de.scrum_master.app.Application.doSomething_aroundBody0(Application.java:9) 
    at de.scrum_master.app.Application.doSomething_aroundBody1$advice(Application.java:22) 
    at de.scrum_master.app.Application.doSomething(Application.java:1) 
    at de.scrum_master.app.Application.main(Application.java:5) 

は私の答えに関連するフォローアップの質問をお気軽に。


アップデート:私はあなたのRetryingCallableRetryPolicyのクラス/インタフェースがどのように機能するか見当がつかない、あなたは私にそれについて多くを教えてくれませんでした。しかし、私は何かを作り、それは次のように働いてしまった:ログ出力はかなり似たものになります

package de.scrum_master.aspect; 

import java.util.concurrent.Callable; 

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.DeclarePrecedence; 

import de.scrum_master.app.DefaultRetryPolicy; 
import de.scrum_master.app.RetryPolicy; 
import de.scrum_master.app.RetryingCallable; 

@Aspect 
@DeclarePrecedence("RetryInterceptor, *") 
public class RetryInterceptor { 
    private RetryPolicy<Object> retryPolicy = new DefaultRetryPolicy<>(); 

    @Around("execution(* doSomething())") 
    public Object invoke(ProceedingJoinPoint thisJoinPoint) throws Throwable { 
     System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint); 
     return RetryingCallable.newRetryingCallable(
      new Callable<Object>() { 
       @Override 
       public Object call() throws Exception { 
        return thisJoinPoint.proceed(); 
       } 
      }, 
      retryPolicy 
     ).call(); 
    } 
} 

package de.scrum_master.app; 

import java.util.concurrent.Callable; 

public interface RetryPolicy<V> { 
    V apply(Callable<V> callable) throws Exception; 
} 
package de.scrum_master.app; 

import java.util.concurrent.Callable; 

public class DefaultRetryPolicy<V> implements RetryPolicy<V> { 
    private static int MAX_TRIES = 5; 
    private static int WAIT_MILLIS_BETWEEN_TRIES = 1000; 

    @Override 
    public V apply(Callable<V> callable) throws Exception { 
     Exception throttlingException = null; 
     for (int i = 1; i <= MAX_TRIES; i++) { 
      try { 
       return callable.call(); 
      } 
      catch (ThrottlingException e) { 
       throttlingException = e; 
       System.out.println(" Throttled during try #" + i); 
       Thread.sleep(WAIT_MILLIS_BETWEEN_TRIES); 
      } 
     } 
     throw throttlingException; 
    } 
} 
package de.scrum_master.app; 

import java.util.concurrent.Callable; 

public class RetryingCallable<V> { 
    private RetryPolicy<V> retryPolicy; 
    private Callable<V> callable; 

    public RetryingCallable(Callable<V> callable, RetryPolicy<V> retryPolicy) { 
     this.callable = callable; 
     this.retryPolicy = retryPolicy; 
    } 

    public static <V> RetryingCallable<V> newRetryingCallable(Callable<V> callable, RetryPolicy<V> retryPolicy) { 
     return new RetryingCallable<V>(callable, retryPolicy); 
    } 

    public V call() throws Exception { 
     return retryPolicy.apply(callable); 
    } 
} 

さて、このような再試行インターセプタを変更しますあなたは前に見た。私にとってこれはうまく動作します。

+0

お返事ありがとうございます。私はすでに '@ Ordered'を使用しています。私は '@ ThrowsException'アドバイスを使用しているので、ここでは1つの質問です。私は無限ループに走っています。しかし、あなたのコードは '@Around'で正常に動作しているようですが、私がここで欠けている微妙な違いは何ですか? – AgentX

+0

おそらく '@ AfterThrowing'を意味します。名前の通り、そのアドバイス型は、メソッドの実行を傍受します。@終了した後、 '@Around'自体がラップします。つまり、前後に何かできること、例外をキャッチして処理したり、メソッドの実行を再試行したりします。 '@ AfterThrowing' isはすでに遅すぎます。例外がスローされ、メソッドの実行が終了しました。さらに、私のログの例外コールスタックをチェックしてください。例外は 'ThrottlingInterceptor.invoke'にスローされるので、間違った' @AfterThrowing'のポイントカットがあったかもしれません。 – kriegaex

+0

ありがとう、私はループを追加し、あなたが説明したようにこれをやり直しましたが、これは今のところうまくいきます。一つの最後のことです:将来私は上記の例で使っていたように( '3 retries'、' randomness'と '指数バックオフ'のような良いデフォルトを提供する)一般的な再試行ポリシーを使いたいと思います。 'return RetryingCallable .NewRetryingCallable(new Callable () ')カスタムループを使用する代わりに、どのように入力すればよいか入力しますか? – AgentX

関連する問題