Spring Framework 入門記 AOPその3 Static Pointcut

どうにかというか,ようやくというか,SpringのAOPも感じがつかめてきました.ここからは「Spring Reference Documentation」に従って詳細に理解していきたいと思います.
まずは「5.1. Concepts」ですが,ここではAOPの概念について簡単に説明しています.その中からちょっと気になることや意外なことをピックアップすると...

  • Springのコンテナ(BeanFactoryApplicationContext)はAOPに依存しません.
  • Springでは,AspectはAdvisorまたはInterceptorとして実装されます.
  • Adviceとして,Around,Before,Throws,After returningをサポートします.After returningは,Joinpointが正常終了した後に実行されるAdviceで,例外がスローされた場合などは実行されません.
  • introductionとして,interfaceの追加をサポートします.
  • SpringのAOPフレームワークは,Adviceを含んだオブジェクトとしてAOP Proxyを作成します.これには,JDK dynamic proxy(java.lang.reflect.Proxyによると思われます)またはCGLIB proxy(バイトコード・エンジニアリングですね)があります.
  • Around Adviceは,AOP AllianceのInterceptorとして実装されます.これは,Spring以外のAOP Allicanceに準拠したコンテナ(JACやNanningなど)で実行することが出来ます.
  • SpringのAOPに対するアプローチは他のコンテナとは異なります.その目標は完全なAOP実装を提供することではなく,AOP実装とSprigのIoCを統合することにあります.

最後のやつが目を引きますね.だからコンテナでAOPを扱わないわけかぁ.その良し悪しはともかく,そういう意図があるらしいということで.
ま,「5.1. Concepts」はこんな感じです.


ということで,「5.2. Pointcuts in Spring」へ進みます.
Springで独自のPointcutを実装するには,Pointcutインタフェースをimplementsします.このインタフェースは,次のメソッドを持っています.

  • getClassFilter()
  • getMethodMatcher()

なんとなく雰囲気が分かりますね.あるJoinpointに対して,そのクラスおよびメソッドがマッチするかを判定するオブジェクト(ルール)を保持しているのがPointcutの実装クラスということですね.PointcutClassFilterMethodMatcherの二つから構成されているのは,再利用性を考慮したためと,粒度の小さな操作を組み合わせる(複数のMethodMatcherのorを取るなど)ことを可能にするためだそうです.
では,ClassFilterはというと,次のメソッドを持っています.

  • matches(Class)

簡単そうですね.引数で受け取ったClassが,このPointcutにマッチするならtrueを返せばいいだけです.
もう一つのMethodMatcherはもうちょっと複雑で,次の3つのメソッドを持っています.

  • matches(Method, Class)
  • isRuntime()
  • matches(Method, Class, Object[])

matches(Method, Class)はなんとなく分かりますね.引数で渡されたMethodがこのPointcutにマッチするならtrueを返せばいいわけです.Classもあるのはなぜでしょう? ClassFilterとダブっているように思えるのですが.
2番目のisRuntime()は,このPointcutがJoinpoint呼び出しの際の引数を参照する必要がある場合にtrueを返すとのことです.そうすると,Joinpointが実際に呼び出されるたびに3番目のメソッドであるmatches(Method, Class, Object[])が呼び出されます.このメソッドの3番目の引数は,Joinpointが呼び出された際の引数の配列です.もしisRuntime()falseを返せば,そのPointcutはAOP Proxyを作成する(Weavingする)時にmatches(Method, Class)が呼び出されるだけで,実際のJoinpoint呼び出し時にはどのメソッドも一切呼び出されないそうです.パフォーマンスのための最適化を図っているわけですね.このようなisRuntime()falseを返すPointcutは,Static pointcutと呼ばれます.一方,trueを返すPointcutはDynamic pointcutと呼ばれます.Dynamic pointcutの場合は,Joinpointが呼び出されるたびにmatches(Method, Class, Object[])が呼び出されます.
前回使用したNameMatchMethodPointcutはStatic pointcutです.
ということで,今回はStatic pointcutを作ってみることにします.とはいっても,すでにNameMatchMethodPointcutRegexpMethodPointcutがあるわけですし,役に立つようなPointcutっていうのは思いつきません.無念だ.しょうがないので無意味であることは十二分に承知の上で*1,Joinpointの引数の数がプロパティで指定された数と一致するものを引っ掛けるというPointcutを作ってみることにします.
Static pointcutを作るための抽象クラスとして,StaticMethodMatcherPointcutおよびStaticMethodMatherPointcutAdvisorが用意されているので,これをベースに使いましょう.なお,後者は前者の派生クラスです.それにしてもクラス名が長いな...
StaticMethodMatherPointcutAdvisorは,ここまでに出てきたPointcutMethodMatcherの各interfaceに加えて,Advisorimplementsしたクラスです.Pointcut#getClassFilter()は,常にtrueとなる実装を返すようになっています.MethodMatcher#isRuntime()は常にfalseを返します.そして,Pointcut#getMethodMatcher()は,戻り値として自分を返すという実装になっています.MethodMatcher#matches(Method, Class)は未定義ですから,これを実装してあげれば出来上がりです.
さぁ,もう材料はそろいました.後は作るだけです.ということで,こんな風になりました.

package study;
import java.lang.reflect.Method;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class ArgumentCountMatcherPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    public boolean matches(Method method, Class targetClass) {
        return method.getParameterTypes().length == count;
    }
}

うんうん,簡単ですね.名前はSpring流に長めにしてみました.ムフッ
次にAspectを適用するクラス.

package study;

public class Foo {
    public void zero() {
        System.out.println("zero");
    }
    public void one(int i) {
        System.out.println("one");
    }
    public void two(int i, String s) {
        System.out.println("two");
    }
}

またしても何の役にも立ちません.心より恥じる.
そして定義ファイル.

<?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="study.ArgumentCountMatcherPointcutAdvisor">
        <property name="advice"><ref bean="interceptor"/></property>
        <property name="count"><value>1</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>

前回とほとんど同じです.今回もまた,AdviceにはDebugInterceptorを使います.
ArgumentCountMatcherPointcutAdvisorcountプロパティには,1を設定しています.ですから,引数が1個のメソッドにだけAdviceが適用されるはずです.
そして実行用のクラスですが,前回使ったMainfirst()second()third()zero()one()two()に変わっただけなので省略します.
それを実行すると...

zero
Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.one(int)] args=[Ljava.lang.Object;@4865ce] target is of class study.Foo]
one
Debug interceptor: next returned
two

大・成・功
次回はDynamic pintcutを作ってみます.

*1:役に立つ例なんて見たことないって? いいんです,例なんてそういうものなんですよ(開き直り).