Advice
3 つの advice があります。
before | before(Formals): Pointcut { Body } |
after | after(Formals) : Pointcut { Body } |
after(Formals) returning [ (Formal) ]: Pointcut { Body } | |
after(Formals) throwing [ (Formal) ]: Pointcut { Body } | |
around | Type around(Formals) [ throws TypeList ] : Pointcut { Body } |
以下では AspectJ でサポートされているこれら 3 つの advice についての豊富なサンプルコードを紹介します。
クイックリンク:
adivce についての公式なドキュメントについては以下を参照してください: 以下で紹介する advice の使い方のサンプルコードの量については、この記事のほうが上記のドキュメントより優っています。しかし adivce とは何かやその他の正式な advice で何ができて何ができないのか、あるいは、どんな書き方ができてどんな書き方ができないのかの記述については、上記のドキュメントに目を通すことをお勧めします。before advice
before advice - その 1
最初の before advice の例は、とてもシンプルなものです。通常のクラスである Hello クラスは "Hello!" を出力するだけのメソッドである hello() を定義しています。BeforeHelloAspect アスペクトを使って、hello() メソッドの 呼び出し の 前 に "Before!" という文字列を出力することがこの例での目的です。
public class Hello { public void hello() { System.out.println("Hello!"); } }
public class Main { public static void main(String[] args) { new Hello().hello(); } }以下の BeforeHelloAspect アスペクトは、1 つの pointcut と 1 つの before advice を定義しています。pointcut beforeHello() では、call pointcut を使って、Hello クラスの void hello() メソッドの呼び出しを選び出しています:
public aspect BeforeHelloAspect { pointcut beforeHello(): call( void Hello.hello() ); // Hello クラスの hello() メソッドが呼ばれた (call) 地点 before(): beforeHello() { System.out.println("Before!"); } }プログラムの実行結果は以下のようになります。
java> java -cp dest aoptest.Main Before! Hello!また、pointcut の定義は省略することもできます:
public aspect BeforeHelloAspect { before(): call( void Hello.hello() ) { System.out.println("Before!"); } }
before advice - その 2
この例では、例外をスローするかもしれないメソッドに対して before advice を適用している場合に、実際にその例外がスローされたらどういう結果になるのかを見てみます。
public class Main { public static void main(String[] args) { try { Class.forName("xxx"); // xxx は存在しないクラス名なので // ClassNotFoundException をスローする。 } catch (ClassNotFoundException e) { System.out.println(e); } } }
public aspect MainAspect { before(): call( Class Class.forName(String) ) { System.out.println("before"); } }
java> java -cp dest aoptest.Main before java.lang.ClassNotFoundException: xxx
after advice
とはいえ after advice は before advice と比べるとやや複雑です。その複雑さは、たとえば、メソッドの呼び出しを考えてみた場合でも、いくつかの特別な状況が考えられるためです。1 つ目は、メソッドが例外をスローした後に、advice のコードを実行する場合です。もう 1 つは、メソッドが通常通りに終了した後に、advice のコードを実行する場合です。最後は、メソッドが例外をスローしたとしても、メソッドが通常に終了したとしても、その後に advice のコードを実行する場合です。
AspectJ では、これら 3 つの状況を別々に扱うことができます。
以下の after advice の例では、まず、メソッドが例外をスローしたとしても、メソッドが通常に終了したとしても、その後に advice のコードを実行するケースについてを扱っています。その次に、メソッドが通常通りに終了した後のケースを、最後に、例外がスローされた後のケースについてを扱っています。
after advice - その 1
after advice の最初の例は before advice の最初の例とほとんど同じです。アスペクトを使ってやりたいことは Hello クラスの hello() メソッドが呼ばれた後になんらかの処理を行うことです。ここでは、単に "After!" という文字列を出力します。
public class Hello { public void hello() { System.out.println("Hello!"); } }
public class Main { public static void main(String[] args) { new Hello().hello(); } }
public aspect AfterHelloAspect { after(): call( void Hello.hello() ) { System.out.println("After!"); } }
java> java -cp dest aoptest.Main Hello! After!
after advice - その 2
この例では、例外を考慮しています。あるメソッドの呼び出しがチェックされる例外 (checked exception) をスローするかもしれない場合に after advice を適用したらどうなるのでしょうか?
この例では、チェックされる例外をスローするかもしれないメソッドの呼び出しとして、Class クラスの static なメソッドである Class forName(String className)
メソッドを考えます。
以下の Hello クラスのコードから見られるように、"xxx" という存在しないクラス名を引数として、Class クラスの static なメソッドである forName(String className) メソッドを呼び出しています。当然、存在しないクラス名を引数としているので forName メソッドは ClassNotFoundException 例外をスローします:
public class Main { public static void main(String[] args) { try { Class.forName("xxx"); // "xxx" というクラスは存在しないので // ClassNotFoundException がスローされる。 } catch (ClassNotFoundException e) { System.out.println(e); } } }考えるアスペクト自体は、単純なものです。今までのサンプルコードと同じように、call pointcut を使って Class クラスの
Class forName(String)
メソッドを選び出します。そして "after" という文字列を出力します:
public aspect MainAspect { after(): call( Class Class.forName(String) ) { System.out.println("after"); } }実行結果は以下のようになります:
java> java -cp dest aoptest.Main after java.lang.ClassNotFoundException: xxx
after advice - その 3
この例では、実行時例外 (run-time exception) に対する after advice を考えます。
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }method() メソッドのように、実行時例外をスローするメソッドに対して、after advice を適用したとすると、その実行結果はどのようなものになるのでしょうか?
public aspect MainAspect { after(): call( void Main.method() ) { System.out.println("after"); } }以下のような結果になります。
java> java -cp dest aoptest.Main after java.lang.RuntimeException
after returning advice
after | after(Formals) : Pointcut { Body } |
after(Formals) returning [ (Formal) ]: Pointcut { Body } | |
after(Formals) throwing [ (Formal) ]: Pointcut { Body } |
以下のいくつかの例では returning を伴う after advice についてを考えます。
after returning advice を使えば、例えば、メソッドが正常に終了した後で何らかの処理を行うことができます。もし、そのメソッドが例外をスローしたとすると advice は実行されません。
また after returning advice は、メソッドが戻り値を返す場合、その値にアクセスすることもできます。
以下では、これらのケースのそれぞれについてサンプルコードを紹介しています。
after advice - その 4 : returning - その 1
この例での、クラス (Main) とアスペクト (MainAspect) は、前の「例 3」のものとほとんど同じです。「例 3」では、Main クラスの method()
メソッドが例外をスローした後でも、MainAspect アスペクトで定義されたコード ("after" と表示する) は実行されました。
一方、この例では after returning advice を考えているので、定義されたコード ("after" と表示する) は、メソッドが正常に終了した場合にのみにしか実行されません。以下の Main クラスの実装から見られるように method()
メソッドは例外をスローしています。したがって、文字列は表示されません。
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }
public aspect MainAspect { after() returning : call( void Main.method() ) { System.out.println("after"); } }
java> java -cp dest aoptest.Main java.lang.RuntimeExceptionもし
method()
メソッドが以下のように例外をスローしないとすると advice は期待したとおりに実行されます:
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { // 前と一緒 after() returning : call( void Main.method() ) { System.out.println("after"); } }
java> java -cp dest aoptest.Main method after
after advice - その 5 : returning - その 2
returning を伴った after advice の 1 つの特徴に、advice の本体内でその戻り値にアクセスできる、というものがあります。この例では、そのような例を紹介します。
public class Main { public static void main(String[] args) { String str = new Main().getString(); System.out.println(str); } public String getString() { return "aaa"; } }
public aspect MainAspect { after() returning(String str) : call( String Main.getString() ) { System.out.println("after"); System.out.println("str: " + str); } }以下のような実行結果になります。
java> java -cp dest aoptest.Main after str: aaa aaa
after advice - その 6 : returning - その 3
public class Main { public static void main(String[] args) { String str = new Main().getString(); System.out.println(str); } public String getString() { return "aaa"; } }
public aspect MainAspect { after() returning(Object obj) : call( String Main.getString() ) { System.out.println("after - obj: " + obj); } }
java> java -cp dest aoptest.Main after - obj: aaa aaa例その 2:
public class Main { public static void main(String[] args) { System.out.println( new Main().getString() ); System.out.println( new Main().getInteger() ); } public String getString() { return "aaa"; } public Integer getInteger() { return new Integer(10); } }
public aspect MainAspect { after() returning(Object obj) : call( * Main.*() ) { System.out.println("after - obj: " + obj); } }
java> java -cp dest aoptest.Main after - obj: aaa aaa after - obj: 10 10例その 3:
public class Main { // 前と変更なし public static void main(String[] args) { System.out.println( new Main().getString() ); System.out.println( new Main().getInteger() ); } public String getString() { return "aaa"; } public Integer getInteger() { return new Integer(10); } }
public aspect MainAspect { after() returning(String str) : call( * Main.*() ) { System.out.println("after - str: " + str); } }
java> java -cp dest aoptest.Main after - str: aaa aaa 10
after advice - その 7 : returning - その 4
public class Main { public static void main(String[] args) { Object obj = new Main().getObject(); System.out.println( obj ); } public Object getObject() { return "aaa"; } }
public aspect MainAspect { after() returning(String str) : call( Object Main.getObject() ) { System.out.println("after - str: " + str); System.out.println(" length: " + str.length() ); } }
java> java -cp dest aoptest.Main after - str: aaa length: 3 aaa例 2:
public class Main { public static void main(String[] args) { Object obj = new Main().getObject(); System.out.println( obj ); } public Object getObject() { return new Integer(10); } }
public aspect MainAspect { // 前と変更なし after() returning(String str) : call( Object Main.getObject() ) { System.out.println("after - str: " + str); System.out.println(" length: " + str.length()); } }
java> java -cp dest aoptest.Main 10
after advice - その 8 : returning - その 5
public class Main { public static void main(String[] args) { int i = new Main().getInt(); System.out.println( i ); } public int getInt() { return 10; } }
public aspect MainAspect { after() returning(int i) : call( int Main.getInt() ) { System.out.println("after - int: " + i); } }
java> java -cp dest aoptest.Main after - int: 10 10できそうでできない例 - その 1:
public class Main { public static void main(String[] args) { int i = new Main().getInt(); System.out.println( i ); } public int getInt() { return 10; } }
public aspect MainAspect { after() returning(Integer i) : call( int Main.getInt() ) { System.out.println("after - integer: " + i); } }
java> java -cp dest aoptest.Main 10できそうでできない例 - その 2:
public class Main { public static void main(String[] args) { Integer i = new Main().getInteger(); System.out.println( i ); } public Integer getInteger() { return new Integer(10); } }
public aspect MainAspect { after() returning(int i) : call( Integer Main.getInteger() ) { System.out.println("after - int: " + i); } }
java> java -cp dest aoptest.Main 10できなさそうだけど、できる例:
public class Main { public static void main(String[] args) { int i = new Main().getInt(); System.out.println( i ); } public int getInt() { return 10; } }
public aspect MainAspect { after() returning(Object obj) : call( int Main.getInt() ) { System.out.println("after - obj: " + obj); System.out.println(" type: " + obj.getClass()); } }
after - obj: 10 type: class java.lang.Integer 10
after advice - その 9 : throwing - その 1
after throwing advice は、例外がスローされた場合にのみに実行されます。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { after() throwing : call( void Main.method() ) { System.out.println("after"); } }
java> java -cp dest aoptest.Main method
after advice - その 10 : throwing - その 2
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() throws Exception { throw new Exception(); } }
public aspect MainAspect { after() throwing : call( void Main.method() ) { System.out.println("after"); } }after throwing advice は例外がスローされた後に実行されるので、以下のような結果になります。
java> java -cp dest aoptest.Main after java.lang.Exception
after advice - その 11 : throwing - その 3
実行時例外がスローされる場合について。
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }
public aspect MainAspect { after() throwing : call( void Main.method() ) { System.out.println("after"); } }
java> java -cp dest aoptest.Main after java.lang.RuntimeException
after advice - その 12 : throwing - その 4
例外オブジェクトへのアクセスについて。
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() throws Exception { throw new Exception(); } }
public aspect MainAspect { after() throwing(Exception e) : call( void Main.method() ) { System.out.println("after - exception: " + e); } }
java> java -cp dest aoptest.Main after - exception: java.lang.Exception java.lang.Exception
after advice - その 13 : throwing - その 5
実行時例外オブジェクトへのアクセスについて。
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }
public aspect MainAspect { after() throwing(RuntimeException e) : call( void Main.method() ) { System.out.println("after - exception: " + e); } }
java> java -cp dest aoptest.Main after - exception: java.lang.RuntimeException java.lang.RuntimeException例その 2: RuntimeException は Exception クラスを継承しているので Exception の型としてもアクセスできます:
public class Main { // 上と変更なし public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }
public aspect MainAspect { after() throwing(Exception e) : call( void Main.method() ) { System.out.println("after - exception: " + e); } }
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() { throw new RuntimeException(); } }
java> java -cp dest aoptest.Main after - exception: java.lang.RuntimeException java.lang.RuntimeException
after advice - その 14 : throwing - その 6
public class MyException extends Exception { }
public class Main { public static void main(String[] args) { try { new Main().method(); } catch (Exception e) { System.out.println(e); } } public void method() throws Exception { throw new MyException(); } }
public aspect MainAspect { after() throwing(MyException e) : call( void Main.method() ) { System.out.println("after - exception: " + e); } }
java> java -cp dest aoptest.Main after - exception: aj.MyException aj.MyException例その 2:
public class MyExceptionA extends Exception { }
public class MyExceptionB extends Exception { }
public class Main { public static void main(String[] args) { try { new Main().method(true); } catch (Exception e) { System.out.println(e); } System.out.println("---"); try { new Main().method(false); } catch (Exception e) { System.out.println(e); } } public void method(boolean b) throws Exception { if (b) { throw new MyExceptionA(); } else { throw new MyExceptionB(); } } }
public aspect MainAspect { after() throwing(MyExceptionA e) : call( void Main.method(boolean) ) { System.out.println("after - exception: " + e); } }
java> java -cp dest aoptest.Main after - exception: aj.MyExceptionA aj.MyExceptionA --- aj.MyExceptionB
around advice
around advice - その 1 : なにもしない
この例では、オリジナルのコード (Java 側のコード) ではなんらかの処理が行われるようになっているのに、around advice を使うことで、何もしないようなコードに変更できることを紹介します。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { void around() : call( void Main.method() ) { // 元のメソッドの代わりに、何もしない } }
java>java -cp dest aoptest.Main // 何もなし
around advice - その 2 : オリジナルを呼び出す
前の例では、元々なんらかの処理が期待されていたコードを around advice を使うことで完全に何の処理も行わないコードで置き換えました。この例では、around advice からオリジナルのコード (join point) を呼び出すことができる特別な proceed() 構文の使い方を紹介します。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { void around() : call( void Main.method() ) { proceed(); } }以下のような実行結果になります。
java> java -cp dest aoptest.Main method
around advice - その 3 : 何度も呼び出す
proceed() は、何度でも呼び出すことができます。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { void around() : call( void Main.method() ) { proceed(); proceed(); } }
java> java -cp dest aoptest.Main method method
around advice - その 4 : 自由に
around advice では、proceed() 構文を使うことで、join point を いつでも 呼び出すことができます。つまり、新たなコードを追加して join point の前後で好きなことができます。この例では、そのような例を紹介します。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { void around() : call( void Main.method() ) { System.out.println("before"); proceed(); System.out.println("after"); } }
java> java -cp dest aoptest.Main before method after
around advice - その 5
この例では、join point がなんらかの戻り値を伴っている場合の around advice の使い方を紹介します。
public class Main { public static void main(String[] args) { String str = new Main().getString(); System.out.println(str); } public String getString() { return "aaa"; } }
public aspect MainAspect { String around() : call( String Main.getString() ) { return "xxx"; } }
java> java -cp dest aoptest.Main xxx例 2: この例では、around advice の戻り値の型とメソッド呼び出しの戻り値の型が一致していますが、一致しないようなケースも許されます。
public class Main { // 上と変更なし public static void main(String[] args) { String str = new Main().getString(); System.out.println(str); } public String getString() { return "aaa"; } }
public aspect MainAspect { Object around() : call( String Main.getString() ) { return "xxx"; } }
java> java -cp dest aoptest.Main xxxしかし、このようなケースでは、もし型が一致していないと ClassCastException がスローするかもしれません:
public class Main { // 上と変更なし public static void main(String[] args) { String str = new Main().getString(); System.out.println(str); } public String getString() { return "aaa"; } }
public aspect MainAspect { Object around() : call( String Main.getString() ) { return new Integer(10); } }
java> java -cp dest aoptest.Main Exception in thread "main" java.lang.ClassCastException at aj.Main.main(Main.java:6)
around advice - その 6
proceed() が、引数をとるケースもあります。この例では、そのようなコードを紹介します。
public class Main { public static void main(String[] args) { new Main().setString("aaa"); } public void setString(String str) { System.out.println(str); } }
public aspect MainAspect { void around(String str) : call( void Main.setString(String) ) && args(str) { proceed("xxx"); } }
java> java -cp dest aoptest.Main xxx
advice の書き方
pointcut の省略
今までの例で見てきたように、pointcut を直接的に指定して advice を書くこともできます:
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { before(): call( void Main.method() ) { System.out.println("before"); } }
java> java -cp dest aoptest.Main before method
複数の advice
ひとつのアスペクトに複数の advice を同時に書こともできます。
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } }
public aspect MainAspect { before() : call( void Main.method() ) { System.out.println("before"); } after() : call( void Main.method() ) { System.out.println("after"); } }
java> java -cp dest aoptest.Main before method after
クラスからの pointcut の定義の参照
pointcut の定義は、通常のクラスないにも書くことができます。アスペクト内でその pointcut を指定するには ClassName.pointcutName
とします:
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } pointcut methodCall() : call( void Main.method() ); }
public aspect MainAspect { before() : Main.methodCall() { System.out.println("before"); } }
java> java -cp dest aoptest.Main before method一方 advice そのものは、通常のクラス内には、定義できません:
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } // コンパイルエラー! before() : call( void Main.method() ) { System.out.println("before"); } }しかし、アスペクト自体は、通常のクラス内に出来ます。次の節の例を見てください。
通常の Java クラスの中での advice
アスペクトはインナーアスペクトとして通常のクラス内に書くこともできます:
public class Main { public static void main(String[] args) { new Main().method(); } public void method() { System.out.println("method"); } private static aspect MainAspect { before(): call( void Main.method() ) { System.out.println("before"); } } }
java> java -cp dest aoptest.Main before methodただし、クラス内で定義するアスペクトは static で宣言されていなければならないことに注意してください:
public class Main { // ... 上と同じ // static で宣言されていないのでコンパイルエラー! private aspect MainAspect { before(): call( void Main.method() ) { System.out.println("before"); } } }
更新履歴
todo
public aspect ComponentAspect { private pointcut pc() : if ( check() ); private pointcut method() : call( void Component.method() ); private static boolean check() { return false; } before() : method() && pc() { System.out.println("before"); } }みたいに ComponentAspect.check() とかかなくてもいけること。