Spring Framework 入門記 AOPその15 TargetSource

今回は「5.10. Using TargetSources」に進みます.なんか,いつのまにか5章もあと少しみたい? 確かに,Spring AOPの基本的なことは大体分かってきた感じがします.便利なautoproxyも使えるようになってきたし.よし,この調子でがんばるべし!
で,今回のTargetSourceですが,なんかちょっと分かりにくい存在です.これは,Aspectを適用するターゲットオブジェクトをAOP Proxyに提供することを役割としています.DataSourceSourceと同じ意味なんでしょうね.
AOP Proxyは,メソッドが呼び出されるたびに,Adviceをかましながら最後はターゲットのメソッドを呼び出すわけですが,その都度ターゲットをTargetSourceから取得するということを,実はこれまでも水面下で行っていたようです.ふー,Springって本当に複雑だなぁ.
TargetSourceが存在することにより,例えばJDBCConnectionAspectをWeavingしたい,なんて場合に重宝するのかもしれません.コネクションプールを使う場合,JDBCConnectionAspectを適用するのはなかなか難しいように感じます.普通に考えると,コンテナが扱うのは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 が行われるため,良い子は使わないほうがよさげです.

これまで使ってきたProxyFactoryBeanAutoProxyCreatorを使った場合,デフォルトではSingletonTargetSourceが使われます.これを変更するには,ProxyFactoryBean#setTargetSource(TargetSource)XxxAutoProxyCreator#setCustomTargetSourceCreator(List)で設定します.
それでは実験を始めましょう.
こういう場合は,すでにあるものを使うより,自分で作ったほうがいいに決まってます.いやその,決してCommons Poolを学習するのが面倒だからではありません. あうあう.心より恥じる.
ということで,独自のTargetSourceを作ります.こういう場合は乱数です.何をするか考えるのが面倒になったら乱数なんです.乱数を返すようなTargetSource,その名もRandomTargetSourceを作ってみませう.
問題は,乱数をどのように扱うかです.CGLIBを使ったAOP Proxyではfinalなクラスは扱えないし,java.lang.reflect.Proxyを使ったAOP Proxyではinterfaceが必要なので,finalでかつinterfaceimplementsしていないIntegerとかは扱えません.Numberinterfaceなら... 無念だ.
しょうがないので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の不変性をいぢるってのは... 心より恥じる.