2017-02-07 7 views
0

特定の例外が発生したときに監視を追加しようとしています。AspectJを使用して独自の例外を処理するメソッドを傍受する方法

@Aspect 
public class LogAspect { 

    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint joinPoint, Throwable e){ 
    System.out.println("Some logging stuff"); 
    } 
} 

とテストクラス::私が得る出力として

public class Example { 


    public void divideByZeroWithCatch(){ 
    try{ 
     int a = 5/0; 
    } 
    catch (ArithmeticException e){ 
     System.out.println("Can not divide by zero"); 
    } 
    } 

    public void divideByZeroWithNoCatch(){ 
    int b = 5/0; 
    } 

    public static void main (String [] args){ 
    Example e = new Example(); 
    System.out.println("***** Calling method with catch block *****"); 
    e.divideByZeroWithCatch(); 
    System.out.println("***** Calling method without catch block *****"); 
    e.divideByZeroWithNoCatch(); 
    } 
} 

:私はこのような側面がある場合たとえば は、

***** Calling method with catch block ***** 
Can not divide by zero 
***** Calling method without catch block ***** 
Some logging stuff 

がある場合、私は思っていました私のアドバイスで何かして、対応するcatchブロックでコードを実行し続けると、例外をスローした直後にメソッドの実行を傍受する方法がありますか?

Some logging stuff 
Can not divide by zero 

答えて

4

はい、できます。あなたは例外ハンドラがどこにあるかを知りたい場合は、:クラスExampleがパッケージde.scrum_master.appであると仮定すると、

package de.scrum_master.aspect; 

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

@Aspect 
public class LogAspect { 
    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint thisJoinPoint, Throwable e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 

    @Before("handler(*) && args(e)") 
    public void logCaughtException(JoinPoint thisJoinPoint, Exception e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 
} 

ログ出力:

***** Calling method with catch block ***** 
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException:/by zero 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:13) 
    at de.scrum_master.app.Example.main(Example.java:21) 

アップデートをあなたはhandler()ポイントカットを必要とします簡単な方法があります:囲むジョインポイントの静的部分を使用します。また、パラメータ名や型などの情報を取得することもできます。コード補完を使用して、使用可能なメソッドを確認してください。今、このようなあなたのコード更新

@Before("handler(*) && args(e)") 
public void logCaughtException(
    JoinPoint thisJoinPoint, 
    JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart, 
    Exception e 
) { 
    // Exception handler 
    System.out.println(thisJoinPoint.getSignature() + " -> " + e); 

    // Method signature + parameter types/names 
    MethodSignature methodSignature = (MethodSignature) thisEnclosingJoinPointStaticPart.getSignature(); 
    System.out.println(" " + methodSignature); 
    Class<?>[] paramTypes = methodSignature.getParameterTypes(); 
    String[] paramNames = methodSignature.getParameterNames(); 
    for (int i = 0; i < paramNames.length; i++) 
     System.out.println("  " + paramTypes[i].getName() + " " + paramNames[i]); 

    // Method annotations - attention, reflection! 
    Method method = methodSignature.getMethod(); 
    for (Annotation annotation: method.getAnnotations()) 
     System.out.println(" " + annotation); 
} 

package de.scrum_master.app; 

import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 

@Retention(RetentionPolicy.RUNTIME) 
public @interface MyAnnotation { 
    int id(); 
    String name(); 
    String remark(); 
} 
package de.scrum_master.app; 

public class Example { 
    @MyAnnotation(id = 11, name = "John", remark = "my best friend") 
    public void divideByZeroWithCatch(int dividend, String someText) { 
     try { 
      int a = 5/0; 
     } catch (ArithmeticException e) { 
      System.out.println("Can not divide by zero"); 
     } 
    } 

    public void divideByZeroWithNoCatch() { 
     int b = 5/0; 
    } 

    public static void main(String[] args) { 
     Example e = new Example(); 
     System.out.println("***** Calling method with catch block *****"); 
     e.divideByZeroWithCatch(123, "Hello world!"); 
     System.out.println("***** Calling method without catch block *****"); 
     e.divideByZeroWithNoCatch(); 
    } 
} 

を次にコンソールログは言う:

***** Calling method with catch block ***** 
catch(ArithmeticException) -> java.lang.ArithmeticException:/by zero 
    void de.scrum_master.app.Example.divideByZeroWithCatch(int, String) 
     int dividend 
     java.lang.String someText 
    @de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend) 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14) 
    at de.scrum_master.app.Example.main(Example.java:22) 

それはあなたのために十分であるならば、あなたは大丈夫です。しかし、静的部分は完全な結合点ではないので、そこからパラメータ値にアクセスすることはできません。これを行うには、手帳を手作業で行う必要があります。そして、これはおそらく高価であり、アプリケーションを遅くする可能性があります。しかし、それは価値がある何のために、私はそれを行う方法をお見せ:

package de.scrum_master.aspect; 

import java.lang.annotation.Annotation; 
import java.lang.reflect.Method; 

import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.annotation.AfterThrowing; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Before; 
import org.aspectj.lang.reflect.MethodSignature; 

@Aspect 
public class LogAspect { 
    private ThreadLocal<JoinPoint> enclosingJoinPoint; 

    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint thisJoinPoint, Throwable e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 

    @Before("execution(* *(..)) && within(de.scrum_master.app..*)") 
    public void recordJoinPoint(JoinPoint thisJoinPoint) { 
     if (enclosingJoinPoint == null) 
      enclosingJoinPoint = ThreadLocal.withInitial(() -> thisJoinPoint); 
     else 
      enclosingJoinPoint.set(thisJoinPoint); 
    } 

    @Before("handler(*) && args(e)") 
    public void logCaughtException(JoinPoint thisJoinPoint, Exception e) { 
     // Exception handler 
     System.out.println(thisJoinPoint + " -> " + e); 

     // Method signature + parameter types/names 
     JoinPoint enclosingJP = enclosingJoinPoint.get(); 
     MethodSignature methodSignature = (MethodSignature) enclosingJP.getSignature(); 
     System.out.println(" " + methodSignature); 
     Class<?>[] paramTypes = methodSignature.getParameterTypes(); 
     String[] paramNames = methodSignature.getParameterNames(); 
     Object[] paramValues = enclosingJP.getArgs(); 
     for (int i = 0; i < paramNames.length; i++) 
      System.out.println("  " + paramTypes[i].getName() + " " + paramNames[i] + " = " + paramValues[i]); 

     // Target object upon which method is executed 
     System.out.println(" " + enclosingJP.getTarget()); 

     // Method annotations - attention, reflection! 
     Method method = methodSignature.getMethod(); 
     for (Annotation annotation: method.getAnnotations()) 
      System.out.println(" " + annotation); 
    } 
} 

は、なぜ我々はジョインポイント簿記のためThreadLocalメンバーが必要なのですか?明らかに、そうでなければマルチスレッドアプリケーションでは問題に陥るはずです。

今コンソールログは言う:

***** Calling method with catch block ***** 
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException:/by zero 
    void de.scrum_master.app.Example.divideByZeroWithCatch(int, String) 
     int dividend = 123 
     java.lang.String someText = Hello world! 
    [email protected] 
    @de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend) 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14) 
    at de.scrum_master.app.Example.main(Example.java:22) 
+0

ああ、私はその余分なポイントカットについて知らなかった。それはAspectJのすべての実装で利用できますか?たとえば、私が知っている限り、SpringAspectJの実装では、[there(Spring-pointcut-designators)](https://docs.spring.io/spring/docs/current/)のように、ハンドラの使用を許可していません。 –

+0

私は知っていますが、この質問はAspectJに関するものであり、Spring AOPに関するものではありません。後者はちょうど "AOP lite"であり、リンク先のマニュアルセクションには、AspectJが 'handler()'をサポートし、Spring AOPがサポートしていない灰色のボックス "その他のポイントカットタイプ"が記述されています。しかし、良いニュースは次のとおりです。また、Springがロードタイム織りで完全なAspectJを使用するように設定することもできます(https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html #aop-using-aspectj)を使用します。次に 'handler()'を使うこともできます。 – kriegaex

+0

はい、私はすでに「完全なAspectJ」ではないことを明示的に指摘したいと思います。以前は春のアオの味が私にそこに噛み付いていただけのようです。 –

0

AspectJの機能の実際の実装によっては、それが可能である: 私は得ることができますdivideByZeroWithCatch()を呼び出す場合だから。 kriegaex's answer for how to do itを参照してください。

ただし、特定のシナリオでAspectJの機能を使用すると、すべてのポイントカット定義がサポートされているとは限りません。これの一例は、Spring Framework AOPであり、only supports a subset of AspectJ featuresです。 あなたができることは2つの方法があります:が例外をキャッチしない(キャッチする)例外とキャッチを行う公開された方法の2つの方法があります。このよう :

protected void divideByZeroNoCatch(int arg) { 
    int r = arg/0; 
} 

public void divideByZeroSafe(int arg) { 
    try { 
    divideByZeroNoCatch(arg); 
    } catch(ArithmeticException ae) { 
    logException(ae); 
    } 
} 

その後、あなたはあなたのAfterThrowingを行う能力を与えるであろう、divideByZeroNoCatchにポイントカットすることができます。明らかに、ポイントカットは少し変更する必要があります。 AspectJの実装が非公開メソッドを実装することをサポートしていない場合、これはうまくいきません。

+0

答えが間違っています。チェックアウト[私の](http://stackoverflow.com/a/42093318/1082681)。 – kriegaex

+0

@kriegaex、これを指摘してくれてありがとう。私は自分の答えを編集します。 –

+0

申し訳ありませんが、私の答えの下の別のコメントで述べたように、あなたの編集も偽です。 AspectJの実装は1つだけです。あなたはSpring AOPについて話していますが、Spring AOP!= AspectJです。それらは異なるものであり、前者は基本構文を共有し、後者の小さなサブセットであり、(動的プロキシを使用して)全く異なる方法で実装されますが、AspectJはプロキシを必要としません。 – kriegaex

関連する問題