Spring Framework 入門記 AOPその15 TargetSource
今回は「5.10. Using TargetSources」に進みます.なんか,いつのまにか5章もあと少しみたい? 確かに,Spring AOPの基本的なことは大体分かってきた感じがします.便利なautoproxyも使えるようになってきたし.よし,この調子でがんばるべし!
で,今回のTargetSource
ですが,なんかちょっと分かりにくい存在です.これは,Aspectを適用するターゲットオブジェクトをAOP Proxyに提供することを役割としています.DataSource
のSource
と同じ意味なんでしょうね.
AOP Proxyは,メソッドが呼び出されるたびに,Adviceをかましながら最後はターゲットのメソッドを呼び出すわけですが,その都度ターゲットをTargetSource
から取得するということを,実はこれまでも水面下で行っていたようです.ふー,Springって本当に複雑だなぁ.
TargetSource
が存在することにより,例えばJDBCのConnection
にAspectをWeavingしたい,なんて場合に重宝するのかもしれません.コネクションプールを使う場合,JDBCのConnection
にAspectを適用するのはなかなか難しいように感じます.普通に考えると,コンテナが扱うのはDataSource
であってConnection
ではありません.やるとしたら,Connection
のラッパー(Proxy)を作って,それにAspectをWeavingするとか.ラッパーはプロパティとしてDataSource
を持って,必要に応じて本物のConnection
を取得する,みたいな感じでしょうか.
それを一般化したものが,今回のTargetSource
なのかな? と解釈してみました.つまり,TargetSource
のメリットを平たく言うと,AspectのWeavingを本物のターゲットの取得前に行える! ということではないかと.たぶん.
さて,そのTargetSource
ですが,次のメソッドを持っています.
Object getTarget()
void releaseTarget(Object target)
Class getTargetClass()
boolean isStatic()
AOP Proxyは,getTarget()
でターゲットオブジェクトを取得してそのメソッドを呼び出し,その後releaseTarget(Object)
でターゲットオブジェクトをTargetSource
に戻します.
もし,TargetSource
が常に同じターゲットオブジェクトを返す場合は,isStatic()
でtrue
を返します.その場合,AOP Proxyは毎回getTarget()
を呼び出さないように最適化するかもしれません(Springが現時点で提供するAOP Proxyはやっていないようです).
そして,Springが標準で提供するTargetSource
の実装としては,次のものがあります.
SingletonTargetSource
- デフォルトで使用される
TargetSource
で,常に同じインスタンスを返します. HotSwappableTargetSource
- ターゲットを取り替えられる
TargetSource
です.ターゲットを取り替えるには,swap(Object)
メソッドを使用します. CommonsPoolTargetSource
- Jakarta CommonsのPoolを使用する
TargetSource
です. PrototypeTargetSource
- 毎回コンテナからBeanを取得して返す
TargetSource
です.Beanが(singletonではなく)prototypeである場合に使う価値がありますが,AOP Proxyのメソッド呼び出しのたびにターゲットのインスタンス生成 & Dependency Injection が行われるため,良い子は使わないほうがよさげです.
これまで使ってきたProxyFactoryBean
やAutoProxyCreator
を使った場合,デフォルトではSingletonTargetSource
が使われます.これを変更するには,ProxyFactoryBean#setTargetSource(TargetSource)
やXxxAutoProxyCreator#setCustomTargetSourceCreator(List)
で設定します.
それでは実験を始めましょう.
こういう場合は,すでにあるものを使うより,自分で作ったほうがいいに決まってます.いやその,決してCommons Poolを学習するのが面倒だからではありません. あうあう.心より恥じる.
ということで,独自のTargetSource
を作ります.こういう場合は乱数です.何をするか考えるのが面倒になったら乱数なんです.乱数を返すようなTargetSource
,その名もRandomTargetSource
を作ってみませう.
問題は,乱数をどのように扱うかです.CGLIBを使ったAOP Proxyではfinal
なクラスは扱えないし,java.lang.reflect.Proxy
を使ったAOP Proxyではinterface
が必要なので,final
でかつinterface
をimplements
していないInteger
とかは扱えません.Number
がinterface
なら... 無念だ.
しょうがないのでjava.util.Date
を使いましょう.
package study; import java.util.Date; import java.util.Random; import org.springframework.aop.TargetSource; public class RandomTargetSource implements TargetSource { private Random randomGenerator; public RandomTargetSource() { randomGenerator = new Random(System.currentTimeMillis()); } public Object getTarget() throws Exception { return new Date(randomGenerator.nextLong()); } public void releaseTarget(Object target) throws Exception { } public Class getTargetClass() { return Date.class; } public boolean isStatic() { return false; } }
ちょろいもんです.えっへん.
今回はターゲットがDate
なので,おなじみのFoo
は出番なしです.無念だ.
次に定義ファイル.<bean>
要素以下のみ.
<bean name="interceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="date" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource"> <bean class="study.RandomTargetSource"/> </property> <property name="interceptorNames"> <value>interceptor</value> </property> </bean>
今回はautoproxyではなく,ProxyFactoryBean
を使いました.そのtargetSource
プロパティにRandomTargetSource
を設定しています.Aspect(Interceptor)は,例によってDebugInterceptor
です.心より恥じる.
で,実行用のクラス.今回の中心部はこんな感じ.
Date date = (Date) context.getBean("date"); System.out.println(date.getTime()); System.out.println(date.getTime()); System.out.println(date.getTime());
このように,同じインスタンスのgetTime()
を呼び出してプリントしています.本来Date
は不変オブジェクトなのですが...
実行!!!
Debug interceptor: count=1 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 8860736471533486537 Debug interceptor: count=2 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 5782795183581819694 Debug interceptor: count=3 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 9017716241053679149
今回はターゲットのメソッドの中でプリントしているわけではないので少々見にくいですが,getTime()
の値が毎回異なっていることが確認できます.
それにしても,いくらお遊びとはいえDate
の不変性をいぢるってのは... 心より恥じる.