Spring Framework 入門記 AOPその3 Static Pointcut
どうにかというか,ようやくというか,SpringのAOPも感じがつかめてきました.ここからは「Spring Reference Documentation」に従って詳細に理解していきたいと思います.
まずは「5.1. Concepts」ですが,ここではAOPの概念について簡単に説明しています.その中からちょっと気になることや意外なことをピックアップすると...
- Springのコンテナ(
BeanFactory
,ApplicationContext
)は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
の実装クラスということですね.Pointcut
がClassFilter
とMethodMatcher
の二つから構成されているのは,再利用性を考慮したためと,粒度の小さな操作を組み合わせる(複数の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を作ってみることにします.とはいっても,すでにNameMatchMethodPointcut
やRegexpMethodPointcut
があるわけですし,役に立つようなPointcutっていうのは思いつきません.無念だ.しょうがないので無意味であることは十二分に承知の上で*1,Joinpointの引数の数がプロパティで指定された数と一致するものを引っ掛けるというPointcutを作ってみることにします.
Static pointcutを作るための抽象クラスとして,StaticMethodMatcherPointcut
およびStaticMethodMatherPointcutAdvisor
が用意されているので,これをベースに使いましょう.なお,後者は前者の派生クラスです.それにしてもクラス名が長いな...
StaticMethodMatherPointcutAdvisor
は,ここまでに出てきたPointcut
とMethodMatcher
の各interface
に加えて,Advisor
もimplements
したクラスです.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
を使います.
ArgumentCountMatcherPointcutAdvisor
のcount
プロパティには,1
を設定しています.ですから,引数が1個のメソッドにだけAdviceが適用されるはずです.
そして実行用のクラスですが,前回使ったMain
のfirst()
,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:役に立つ例なんて見たことないって? いいんです,例なんてそういうものなんですよ(開き直り).