今回からAdviceを学習します.
SpringのAdviceは,ライフサイクルとタイプで分類することができます.
ライフサイクルには,次のものがあります.
- per-class
- 全てのターゲットオブジェクトを一つのインスタンスで処理するものです.
- per-instance
- ターゲットオブジェクトのインスタンスごとにAdviceのインスタンスを用意するものです.
per-instanceは,ターゲットオブジェクトにインスタンス変数を追加するようなイメージのAdviceの場合に使用します.
タイプには次のものがあります.
- Interception Around advice
- メソッドJoinpointを包み込むように適用されるAdviceです.
- Before advice
- メソッドJoinpointの実行前に適用されるAdviceです.
- Throws advice
- メソッドJoinpointが例外をスローした場合に適用されるAdviceです.
- After Returning advice
- メソッドJoinpointが例外をスローすることなくリターンした場合に適用されるAdviceです.
- Introduction advice
- ターゲットに
interface
を追加するAdviceです.
今回は,Interception Around adviceを学習します.
Springでは,いわゆるAround AdviceをInterceptorと呼んでいます.これは,Springでそう決めているのではなくて,AOP Allianceの定義しているinterface
をそのまま使っているようです.では,なぜAOP AllianceではInterceptorというのかと思い,ちょろっと調べてみました... よく分かりませんでした.無念だ.
AOP Allianceの定義しているinterface
を見ると,Advice
とInterceptor
は別々に用意されています.推測ですが,AOPの概念としてのAdvice
と,それを実現するメカニズムの一つであるInterceptor
を区別したかったのかな,と思いました.OOPでいうと特化と継承の違いみたいな.
用語の由来はともかく,SpringでAround Adviceを実装するには,AOP Allianceのinterface
であるMethodInterceptor
を実装します.これはInterceptor
をextends
したinterface
で,invoke(MethodInvocation)
というメソッドが一つだけ宣言されています.
MethodInvocation
もまたAOP Allianceが定義しているinterface
で,Joinpointを表しています(1つのJoinpointに複数のAdviceが適用される場合は,内側のAdviceを表現する場合もあります).Interception Around adviceでターゲットオブジェクトのメソッドを実行するには,このMethodInvocation
のproceed()
メソッドを呼び出します.その前後で好きな処理を行ったり,場合によってはproceed()
を呼び出さずにすませたり,好きなようにすればいいわけですね.
ということで,サクッとお試ししましょう.簡単そうなネタとして,「AspectCookBookのRecipesより」で紹介したAsynchronousRoutingRecipeをやってみましょう.任意のメソッドを別スレッドで実行するというAdviceです.任意とはいっても,実質的には戻り値のない(voidな)メソッドでないと意図した結果にならない場合もありそうですが.
package study; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AsyncInterceptor implements MethodInterceptor { public Object invoke(final MethodInvocation invocation) throws Throwable { Thread thread = new Thread() { public void run() { try { invocation.proceed(); } catch (Throwable ignore) { } } }; thread.start(); return null; } }
Thread
のサブクラスを匿名クラスとして作って,そこでMethodInvocation#proceed()
を呼び出しています.そのために,invocation
引数はfinal
宣言しています.
んで,Aspectを適用する対象クラスです.
package study; public class Foo { private String text; public void slowPrint() { try { Thread.sleep(1000); } catch (InterruptedException ignore) { } System.out.println("のそのそ"); } }
こやつのslowPrint()
は,1秒間居眠りしてからメッセージを出力します.
次に定義ファイル.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="study.Foo"/> </property> <property name="interceptorNames"><value>interceptor</value></property> </bean> <bean id="interceptor" class="study.AsyncInterceptor"> </bean> </beans>
今回は,ターゲットになるBeanの定義をProxyBeanFactory
の定義の中に埋め込んでみました.少しだけすっきり?
また,手抜きしてPointcutを指定していないので,ターゲット(Foo
)の全てのメソッドにAdviceが適用されます.本来なら,voidなメソッドだけが対象となるようにPointcutを指定する方が安全でしょう.
で,実行用のクラス.
package study; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.context.ApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; public class Main { public static void main(String[] args) { try { BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(); BeanFactoryReference ref = locator.useBeanFactory("context"); ApplicationContext context = (ApplicationContext) ref.getFactory(); Foo foo = (Foo) context.getBean("foo"); foo.slowPrint(); System.out.println("てきぱき"); ref.release(); } catch (Exception e) { e.printStackTrace(); } } }
ここでは,Foo#slowPrint()
を呼び出した後にメッセージを出力しています.Adviceが適用されていなければFoo#slowPrint()
が出力するメッセージの後に出力されるはずですが...
実行!!!
てきぱき のそのそ
おっけー.