id:torutk:20070505のロギングでの問題点をAspectJで解決してみようと実験します。
まず、MyBusinessクラスからロギングAPIの呼び出しを削除します。横断的関心事であるロギングはアスペクトとして別モジュールに記述するからです。
public class MyBusiness { public int calc(int a, int b) throws MyBusinessException { int returnValue = 0; // 処理1 if (isSpecificCase) { throw new MyBusinessException(); } // 処理2 return returnValue; } private boolean isSpecificCase = false; }
AspectJ言語でMyBusinessクラスのメソッドトレース・ログを記述します。
import java.util.logging.Logger; import org.aspectj.lang.Signature; aspect Trace { pointcut atTrace(): execution(* MyBusiness.*(..)) || execution(MyBusiness.new(..)); before(): atTrace() { Signature sig = thisJoinPoint.getSignature(); logger.entering(sig.getDeclaringType().getName(), sig.getName(), thisJoinPoint.getArgs()); } after() returning(Object o): atTrace() { Signature sig = thisJoinPoint.getSignature(); logger.exiting(sig.getDeclaringType().getName(), sig.getName(), o); } after() throwing(Exception e): atTrace() { Signature sig = thisJoinPoint.getSignature(); logger.throwing(sig.getDeclaringType().getName(), sig.getName(), e); } Logger logger = Logger.getLogger(MyBusiness.class.getName()); }
ポイントカットの定義
AspectJでは、アスペクトを織り込む(ウィービング)場所をpointcutで定義します。ここでは、以下の2つの場所を定義しています。
(1)MyBusinessクラスの任意の名前、任意の戻り値型、任意の引数のメソッドの実行(execution)
(2)MyBusinessクラスの任意の引数のコンストラクタの実行(execution)
任意の名前、任意の戻り値型は、ワイルドカード('*')で示します。一方、任意の引数は、'..'で示します。
ポイントカットの事前に実行するアドバイスの定義
pointcutで定義した場所に処理が到達したときに最初に実行したい内容をbeforeアドバイスで定義します。ここでは、JavaロギングAPIのLogger.enteringメソッドを実行します。enteringメソッドの引数で渡すクラス名、メソッド名、引数の配列を取り出すために、thisJoinPointという暗黙の変数と、Signatureオブジェクトを使用しています。
これは、JavaのリフレクションAPIと類似の概念です。
(1)クラス名:この単純なサンプルではMyBusinessクラスに決めうちでもいいのですが、ここでは汎用的にクラス名を取り出しています。thisJoinPointからSignatureオブジェクトを取得し(getSignature)、そこから型を取り出し(getDeclaringType)、その名前を取り出しています(getName)。
(2)メソッド名:Signatureオブジェクトからメソッド名を取り出しています(getName)。
(3)引数の配列:thisJoinPointから引数配列を取り出しています(getArgs)。
ポイントカットの事後に実行するアドバイスの定義(return)
pointcutで定義した場所から例外ではなく正常にリターンした直後に実行したい内容をafter - returningアドバイスで定義します。ここでは、JavaロギングAPIのLogger.exitingメソッドを実行します。
exitingメソッドは引数にクラス名、メソッド名、戻り値を取ります。このうち
クラス名、メソッド名はbeforeアドバイスと同様thisJoinPointという暗黙の変数と、Signatureオブジェクトを使用します。
戻り値を取得するには、returningのパラメータとして戻り値型を宣言する必要があります。ただしメソッドトレースの対象メソッドは戻り値型がばらばらなため、ここではObject型で宣言しています。Javaのプリミティブ型はObject型ではありませんが、AspectJ(1.5.3)ではうまく捉えられています(仕様?実装依存?)。
ポイントカットの事後に実行するアドバイスの定義(例外)
pointcutで定義した場所から例外で終了する直後に実行したい内容をafter - throwingアドバイスで定義します。ここでは、JavaロギングAPIのLogger.throwingメソッドを実行します。
throwingメソッドは引数にクラス名、メソッド名、例外オブジェクトを取ります。このうちクラス名、メソッド名はbeforeアドバイスと同様thisJoinPointという暗黙の変数と、Signatureオブジェクトを使用します。
例外オブジェクトを取得するには、after-throwingのパラメータとして例外型を宣言する必要があります。例外の型はメソッドによってばらばらなため、ここではException型で宣言しています(Throwable型の方がよいかも)。
AspectJを使ったトレースの例題のコンパイルと実行
- AspectJ1.5.3
- Java SE 6
- Windows Vista
D:\work> ajc MyBusiness.java MyBusinessException.java Trace.aj
D:\work> java -Djava.util.logging.config.file=mylogging.properties MyBusiness
2007/05/06 8:02:33 MyBusiness calc
詳細レベル (中): ENTRY 1,000 2,000
2007/05/06 8:02:33 MyBusiness calc
詳細レベル (中): RETURN 3,150
2007/05/06 8:02:33 MyBusiness calc
詳細レベル (中): ENTRY 1,234 5,678
2007/05/06 8:02:33 MyBusiness calc
詳細レベル (中): THROW
MyBusinessException
at MyBusiness.calc(MyBusiness.java:13)
at MyBusiness.main(MyBusiness.java:24)