Spring Framework 入門記 AOPその6 Interception Around Advice

今回から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を見ると,AdviceInterceptorは別々に用意されています.推測ですが,AOPの概念としてのAdviceと,それを実現するメカニズムの一つであるInterceptorを区別したかったのかな,と思いました.OOPでいうと特化と継承の違いみたいな.
用語の由来はともかく,SpringでAround Adviceを実装するには,AOP AllianceのinterfaceであるMethodInterceptorを実装します.これはInterceptorextendsしたinterfaceで,invoke(MethodInvocation)というメソッドが一つだけ宣言されています.
MethodInvocationもまたAOP Allianceが定義しているinterfaceで,Joinpointを表しています(1つのJoinpointに複数のAdviceが適用される場合は,内側のAdviceを表現する場合もあります).Interception Around adviceでターゲットオブジェクトのメソッドを実行するには,このMethodInvocationproceed()メソッドを呼び出します.その前後で好きな処理を行ったり,場合によっては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()が出力するメッセージの後に出力されるはずですが...
実行!!!

てきぱき
のそのそ

おっけー.