Spring Framework 入門記 AOPその5 Pointcutその他

今回は,Pointcutの仕上げとしてSpringが標準で用意してくれているPointcutの実装クラスについて学習します.
Static pointcut

  • NameMatchMethodPointcut

AOP その2」で使用した,Joinpointのメソッド名でマッチングを行うPointcutです.Advisorインタフェースを実装したNameMatchMethodPointcutAdvisorも用意されています.
このPointcutは,mappedNamesという文字列の配列プロパティを持っています.このプロパティに設定する文字列には,ワイルドカードとして'*'を含めることが出来ます.といっても,先頭または末尾のみ有効なようで,途中に書いても効果はなさそう.無念だ.そういう場合は後述の正規表現を使用するPointcutを使えということでしょうね.
matches(Method, Class)メソッドは,Joinpointのメソッド名がmappedNamesプロパティのいずれかの文字列と一致(ワイルドカード含む場合はマッチ)すればtrueを返します.

  • RegexpMethodPointcut

NameMatchMethodPointcutと同様,Joinpointのメソッド名でマッチングを行うPointcutですが,正規表現を使うことができます.Advisorインタフェースを実装したRegexpMethodPointcutAdvisorも用意されています.
このPointcutは,patternという文字列のプロパティを持っていて,正規表現を設定することが出来ます.正規表現の実装には,Jakarta OROが使用されているようで,たとえJDK1.4で実行する場合でも,java.util.regexは使われないようです.無念だ.OROということでパフォーマンスが気になるところではありますが,このPointcutはStaticであるため,正規表現のマッチングが行われるのはAOP Proxy作成時だけです.あまり神経質になる必要はないかもしれません.
matches(Method, Class)メソッドは,Joinpointのメソッド名が正規表現とマッチすればtrueを返します.

  • RootClassFilter

これはPointcutそのものではなくて,ClassFilterインタフェースの実装です.他のPointcutから利用されることを意図しているのでしょう.コンストラクタでClassを設定することができます.matches(Class>メソッドは,Joinpointのターゲットオブジェクトのクラスが,コンストラクタで渡されたClassに適合する(Class#isAssignableFrom(Class)trueを返す)場合にtrueを返します.


Dynamic pointcut

  • ControlFlowPointcut

AspectJのcflowのようなPointcutで,あるJoinpointが呼び出されている間の全てのJoinpointを採用するというPointcutです.コンストラクタでクラスとメソッド(省略可)を指定することが出来ます.
matches(Method, Class, Object[])は,現在のスタックフレームを調べて,コンストラクタで指定されたクラス・メソッドが含まれている場合にtrueを返します.でもでも,JDK1.3以前のための実装は少し怪しい感じ.メソッド名を省略した場合,クラス名だけでチェックを行うのですが,単純にString#indexOf(String)を使っているので,例えばFooを指定するとFooBarも引っかかってしまうような? JDK1.4以降を使えばいいんですけどね.
スタックフレームを取得してマッチングを行うことから,通常のPointcutよりもかなり遅いので気をつけろとのことです.JDK1.4で5倍くらい,JDK1.3だと10倍以上だとか.
その他

  • ComposablePointcut

複数のPointcutを組み合わせることの出来るPointcutです.組み合わせ方として,Pointcutの和を取るunion,積を取るintersectionを使うことが出来ます.ただし,unionについてはClassFilterおよびMethodMatcherを組み合わせることは出来ますが,Pointcutを組み合わせることは出来ません.そのような場合は,後述のUnionPointcutを使えとのことです.intersectionPointcutも組み合わせることが出来ます.
前回作成したDynamic Pointcutの修正版では,効率を考えてmatches(Method, Class)メソッドとmatches(Method, Class, Object[])の両方を実装しましたが,むしろNameMatchMethodPointcutのようなStatic pointcutとをintersectionして使うほうがSpring流なのかもしれません.

  • UnionPointcut

コンストラクタで設定された二つのPointcutの和を取るPointcutです.
ComposablePointcutともども,Springにしては珍しくプロパティではなくコンストラクタで設定を行います.どちらかというと,定義ファイルに記述して使うよりも,他のPointcutの実装などからプログラマティックに使われることを想定しているのかもしれません.


おおむねRegexpMethodPointcutがあれば困らないような気がするわけですが,あって困るものでもないので機会があればありがたく使わせていただくことにします.
といったところでPointcutは終了にします.


... と思ったのですが,何もコードを書かないのはブログ^h^h^h日記としてどうよ? という気がしたので,とりあえず何かサンプルを作ります.おもしろそうなのはControlFlowPointcutですが,有用そうなのはComposablePointcutだし... そんな場合は両方使ってしまいましょう.うん,その方が素敵(キラッ)!
まずPointcutを作ります.これは,プロパティに設定されたPointcutの配列全てのintersection(積)を取ってくれるようにします.というわけでこんな感じ.

package study;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;

public class IntersectionPointcutAdvisor implements InitializingBean , Pointcut, PointcutAdvisor, Ordered {
    private Pointcut pointcuts;
    private Advice advice;
    private int order = Integer.MAX_VALUE;
    private ComposablePointcut composablePointcut = new ComposablePointcut();

    //InitializingBean
    public void afterPropertiesSet() {
        if (pointcuts != null) {
            for (int i = 0; i < pointcuts.length; ++i) {
                composablePointcut.intersection(pointcuts[i]);
            }
        }
    }

    //Pointcut
    public ClassFilter getClassFilter() {
        return composablePointcut.getClassFilter();
    }
    public MethodMatcher getMethodMatcher() {
        return composablePointcut.getMethodMatcher();
    }

    //PointcutAdvisor
    public Pointcut getPointcut() {
        return composablePointcut;
    }
    public boolean isPerInstance() {
        throw new UnsupportedOperationException("perInstance property of Advisor is not yet supported in Spring");
    }

    //getters and setters
    public Pointcut getPointcuts() {
        return pointcuts;
    }
    public void setPointcuts(Pointcut[] pointcuts) {
        this.pointcuts = pointcuts;
    }
    public Advice getAdvice() {
        return advice;
    }
    public void setAdvice(Advice advice) {
        this.advice = advice;
    }
    public int getOrder() {
        return order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
}

プロパティに設定されたPointcut配列のintersectionを取るためにInitializingBeanimplementsしています.
次に実験用のクラス.今回は,FooBarの二つのクラスを使います.
まずはFoo

package study;

public class Foo {
    private Bar bar;
    public Bar getBar() {
        return bar;
    }
    public void setBar(Bar bar) {
        this.bar = bar;
    }
    public void yoku() {
        System.out.println("よーく");
        kangaeyo();
    }
    public void kangaeyo() {
        System.out.println("考えよー,");
        bar.okaneha();
    }
}

これは,Barへの参照をプロパティで持っています.
次にBar

package study;

public class Bar {
    public void okaneha() {
        System.out.println("お金は");
        daijidayo();
    }
    public void daijidayo() {
        System.out.println("大事だよー.");
    }
}

そして定義ファイルですが,今回はBarの方にAspectをWeavingします.そのAspectのPointcutにはもちろんIntersectionPointcutAdvisorを使います.そのpointcutsプロパティには,RegexpMethodPointcutControlFlowPointcutを設定します.
こんな感じ.

<?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="study.Foo">
        <property name="bar"><ref bean="bar"/></property>
    </bean>

    <bean id="barTarget" class="study.Bar">
    </bean>

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

    <bean id="advisor" class="study.IntersectionPointcutAdvisor">
        <property name="pointcuts">
            <list>
                <bean class="org.springframework.aop.support.RegexpMethodPointcut">
                    <property name="pattern"><value>.*\.okaneha</value></property>
                </bean>
                <bean class="org.springframework.aop.support.ControlFlowPointcut">
                    <constructor-arg index="0"><value>study.Foo</value></constructor-arg>
                </bean>
            </list>
        </property>
        <property name="advice"><ref bean="interceptor"/></property>
    </bean>

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

RegexpMethodPointcutpatternプロパティには".*\.okaneha"を指定しています.実はこのPointcut,マッチングの際に使用するメソッド名は,そのクラスの完全限定名で修飾されていたんですね.ということで,任意のクラス(といっても今回の場合は意味がないのですが)のokaneha()にマッチするように正規表現を指定しました.
ControlFlowPointcutについては,引数が1つのコンストラクタで初期化するようにしています.この場合の引数はClassですので,このPointcutはスタックフレーム中にstudy.Fooが含まれていればマッチすることになります.
この2つのPointcutのIntersectionが,Barに適用される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.yoku();
            Bar bar = (Bar) context.getBean("bar");
            bar.okaneha();

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

そして実行!!!

よーく
考えよー,
Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Bar.okaneha()] args=[Ljava.lang.Object;@1556d12] target is of class study.Bar]
お金は
大事だよー.
Debug interceptor: next returned
お金は
大事だよー.

このように,Fooを経由して呼び出された場合のBar#okaneha()にはAspectが適用されて前後にトレースが出力されています.しかし,Fooを介さずにMain#main()から呼び出された場合にはAspectが適用されていません.ということで,少なくともControlFlowPointcutが効いていることが確認できました.RegexpMethodPointcutの方は,今回の例ではあってもなくても同じ? 心より恥じる.


それにしても,やっぱりこういうものは動かしてみないと分からないことが多々ありますね.今回,実は2つの点ではまりました.無念だ.
一つ目は,すでに書いたようにRegexpMethodPointcutpatternプロパティの指定の仕方です.てっきりメソッド名だけをパターンでマッチングするのだと思ってしまったんですよね.なぜって言われると困るのですが.ということなので,メソッド名に限らずクラス名の部分にも正規表現でフィルタリングすることが出来ます.Springの場合,PointcutもひとつのBeanであり,様々なターゲットに同じPointcutを適用できるので,これは重要なことなのかもしれません.
もうひとつはまってしまったことは,Aspectが適用されるのは,AOP Proxyを経由したメソッド呼び出しの場合だけということです.当然ですよね.でもはまっちゃいました.心より恥じる.今回テスト用に作ったFooBarは,意味もなく自分のクラス(インスタンス)のメソッドを呼び出しています.この場合には,AOP Proxyを経由しないため,Aspectは適用されないんですね.このことになかなか気づくことが出来ませんでした.これって,意外と痛い制約にならないのでしょうか? ちょっとドキドキです.


ということで,今度こそ本当にPointcut終了です.次回からはAdviceです.