引き続き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の親クラスであるDynamicMethodMatcherPointcutAdvisor
のmatches(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[])
は呼び出されないことを確認できました.満足,満足.