Spring Framework 入門記 AOPその2 PointcutとAdvisor

出だしからいきなりつまずいてしまったAOP編,今回はPointcutに挑戦します.独自のAdviceを作るのもいいかな,という考えもあったのですが,作る前にあるものを使うほうが簡単だったり勉強になったりするだろうということで,まずはSpringが用意してくれているPointcutを使ってみようと思ったわけです.
そんなわけでPointcutですが,一応「5.2. Pointcuts in Spring」として説明されています.しかし,ここで説明されているのは主に独自のPointcutの作り方で,それをどう使えばいいのか,ようするにXMLをどう書けばいいのかが分かりません.無念だ.
それでも,ちょっとは役に立つ情報がありました.いえ,すみません,たくさんありました.
まず,RegexpMethodPointcutというクラスが用意されていることが分かりました.Adviceを適用するメソッド名のパターンを正規表現で指定できるというPointcutの実装です.XMLの例もあります.JavaBeansマンセーなSpringですから,Pointcutもまた当然ながらBeanとして扱うことが出来るようです.でも,それをどうやってAdviceと組み合わせるのかが分かりません.心より恥じる.
と,すぐそこにRegexpMethodPointcutAdvisorというものが出ていました.Advisor... ルールエンジンじゃないよなぁ.
ということでドキュメントを検索してみると,始めの方にちゃんと説明がありました.それによると,SpringでのAspectは,AdvisorまたはInterceptorとして実装される,とのことです.むむ... InterceptorはAspect.確かに前回,Interceptorを使いました.それ自体単独で完結している,つまりAspectであるかのように使えました.そしてPointcutなAdvisorがあって,AdvisorもまたAspect... これか!? これがいま探しているものなのか?
... たぶん30分くらい経過*1
よっしゃぁ! 私を誰だと思っている!? 世界一の外科医だぞ!! どぅどぅ.落ち着きたまえ,財前君.
どうにかつながりました.Advisorって,PointcutをAdviceのように使えるようにするためのアダプタみたいなものなのですね.あるいは,PointcutはAdviceを適用するかどうかを決めるフィルタのようなもので,Advisorに使われるというか.そして,AdvisorとAdviceはChain of Responsibilityのようにつなげられるという感じ.
図にするとこんな感じ(うまく表示されてくれ).

Adviceなし Proxy Target
Adviceのみ Proxy Interceptor Target
PointcutとAdvice Proxy Advisor Interceptor Target
↓↑
Pointcut

実際には,Springが標準で用意してくれているAdvisorの多くは,それ自身がPointcutとして実装されています.ただし,それはMustではなく,DefaultPointcutAdvisorというAdvisorの実装クラスは,Pointcutをプロパティで持つようになっています.単なるPointcutはこれと組み合わせればいいわけですね.
それから,ここがちょっとトリッキーというかいまいちな感じなのですが,前回使用したProxyFactoryBeaninterceptorNamesというプロパティには,Interceptorだけでなく,実はAdvisorの名前も渡せるのだそうです.それなら名前を変えてくれ! って思っちゃいました.別にいいけどね.ぶつぶつ.
うん,これで全体が見えてきました.InterceptorAdvisorのプロパティに設定して,それをProxyFactoryBeanのプロパティに設定すれば,全体がつながるわけですね.
ということで,早速Pointcutを組み込んだサンプルを作りましょう.
まずはAspectを適用するターゲットとなるクラスを.今回は,何の役にも立たないメソッドを特別に3つもご用意させていただきました.

package study;

public class Foo {
    public void first() {
        System.out.println("first");
    }
    public void second() {
        System.out.println("second");
    }
    public void third() {
        System.out.println("third");
    }
}

そしてBean定義のXMLファイルを作成します.Interceptorには,前回と同じくDebugInterceptorを使用します.Advisorには,前述の正規表現を使えるものより簡単そうなNameMatchMethodPointcutAdvisorを使用することにします.これは,メソッド名でフィルタリングするPointcutの実装も兼ねたAdvisorです.よくありがちなワイルドカード("*")を使用することが出来ます.
で,こうなりました.

<?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="fooTarget" class="study.Foo">
    </bean>

    <bean id="interceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
    </bean>

    <bean id="advisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice"><ref bean="interceptor"/></property>
        <property name="mappedName"><value>*d</value></property>
    </bean>

    <bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target"><ref bean="fooTarget"/></property>
        <property name="interceptorNames"><value>advisor</value></property>
    </bean>
</beans>

今回,ProxyFactoryBeaninterceptorNamesプロパティに設定しているのは"advisor"と名づけられたBeanです.そして,そのadviceプロパティには,"interceptor"と名づけられたBeanを設定しています.つながってますねぇ.
"advisor"なBeanでは,mappedNameプロパティにフィルタで引っ掛けるメソッド名として"*d"を指定しています.メソッド名が"d"で終了する場合にAdviceを適用するということになります.
そして最後に実行用のクラス.

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.first();
            foo.second();
            foo.third();

            ref.release();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

そして実行!!!

first
Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.second()] args=[Ljava.lang.Object;@15f7107] target is of class study.Foo]
second
Debug interceptor: next returned
Debug interceptor: count=2 invocation=[Invocation: method=[public void study.Foo.third()] args=[Ljava.lang.Object;@15f7107] target is of class study.Foo]
third
Debug interceptor: next returned

"d"で終了するsecond()third()にはDebugInterceptorが適用されていますが,firstには適用されていないことが確認できました.
ふぅ,なんとかPointcutも使えるようになりました.よかったよかった.
それにしても,これくらいのことをできるようになるのに大騒ぎです.心より恥じる.
世間の皆さんはどうやってSpringをマスターしているのだろう? ちょっと疑問を感じてしまいました.
ともかくこれで,とても基本的なことは出来るようになりました.次回からは,一つ一つの要素を詳しく学習していこうと思います.

*1:もっと経過したかも.PSPしていないので実態は不明