Spring Framework 入門記 AOPその4 Dynamic pointcut

引き続きPointcutの学習,今回はDynamic pointcutです.
前回学習したように,Dynamic pointcutはJoinpointであるメソッド呼び出しの際に,毎回Adviceを適用すべきかをチェックすることができるPointcutです.AOP Proxyを作成(Weaving)する時点で役目を終えるStatic pointcutよりも当然オーバーヘッドは大きくなりますが,その分柔軟な対応をすることができるわけですね.
Static pointcutとの違いは次の2点です.

  • MethodMatcher#isRuntime()trueを返す.
  • MethodMatcher#matches(Method, Class, Object[])でAdviceを適用するか判定する.

Dynamic pointcutについても,Springはベースとなる抽象クラスを用意してくれています.

  • DynamicMethodMatcherPointcut
  • DynamicMethodMatcherPointcutAdvisor

すでにisRuntime()実装済みなので,もはやmatches(Method, Class, Object[])を実装すればいいだけです.
というわけで,早速ですが作ってしまいましょう.
相変わらず役立たずですが,今回はStringの引数を1個だけ持つメソッドJoinpointについて,引数の値がプロパティの値と一致する場合にAdviceを適用するようなDynamic pointcutを作ってみます.
で,こうなりました.

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

public class StringDynamicMethodMatcherPointcutAdvisor extends DynamicMethodMatcherPointcutAdvisor {
    private String text;
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public boolean matches(Method method, Class targetClass, Object[] args) {
        return args.length == 1 && args[0] instanceof String && text.equals(args[0]);
    }
}

Springらしく,めいっぱい長いクラス名にしてみました.っていうか親のクラス名の前にStringって付けただけですけど.
matches(Method, Class, Object[])では,Joinpointの引数が1つでその型がStringの場合にtextプロパティの値と比較しています.
で,Aspectを適用するクラス.

package study;

public class Foo {
    public void yada(String text) {
        System.out.println(text);
    }
}

文字列を表示するだけです.絶対ヤダ!*1 もうヤダ!*2
次に定義ファイル.

<?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.StringDynamicMethodMatcherPointcutAdvisor">
        <property name="advice"><ref bean="interceptor"/></property>
        <property name="text"><value>midori</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>

ここでは,Pointcutのtextプロパティに"midori"を指定しているので,Foo#yada(String)が引数"midori"で呼び出された場合だけAdviceが適用されます.Adviceは例によってDebugInterceptorです.
で,実行用のクラス.

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.yada("seri");
            foo.yada("midori");
            foo.yada("misuzu");

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

そして実行!

seri
Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.yada(java.lang.String)] args=[Ljava.lang.Object;@19113f8] target is of class study.Foo]
midori
Debug interceptor: next returned
misuzu

狙いどーりー.


という感じなのですが,ちょっと気になることがあります.作成したPointcutの親クラスであるDynamicMethodMatcherPointcutAdvisormatches(Method, Class)は常にtrueを返すように実装されています.これだと,ターゲットになるクラスの全てのメソッド(Joinpoint)呼び出しに対してPointcutが適用されてしまいます.たいしたオーバーヘッドではないとは思うのですが,それでも毎回引数の数とかチェックするのは嬉しくない感じです.どう考えても,適用しないことが最初から分かっているJoinpointは対象から外してしまいたいですよね.要するに,AOP Proxy生成時に,引数の数と型のチェックは済ませてしまった方が効率的だと思うわけです.そんな場合は,matches(Method, Class)を併用すればいいはず.
ということで次のように修正しました.

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

public class StringDynamicMethodMatcherPointcutAdvisor extends DynamicMethodMatcherPointcutAdvisor {
    private String text;
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public boolean matches(Method method, Class targetClass) {
        Class parameterTypes = method.getParameterTypes();
        return parameterTypes.length == 1 && parameterTypes[0].equals(String.class);
    }
    public boolean matches(Method method, Class targetClass, Object args) {
        return text.equals(args[0]);
    }
}

Fooに引数のないメソッドなどを追加したりして確認したところ,よけいなJoinpointではmatches(Method, Class, Object[])は呼び出されないことを確認できました.満足,満足.

*1:随分前のアメリカンファミリーのCM風

*2:最近のジョージアのCM風