Spring Framework 入門記 AOPその12 My ProxyFactoryBean

からさわぎでSeasarにノックアウトされたので,今日からSeasar入門記です.
嘘です.ごめんなさい.日記的にはより複雑でより難しくて日本語の情報が少なくて頼るところが少ないSpringの方が何かと好都合なので,これからもSpring入門記です.*1
与太話はともかく,今回は「5.6. Convenient proxy creation」です.
SpringのAOPのかぎを握っているのはProxyFactoryBeanです.間違いない.こいつがターゲットにAspect(Interceptor,Advisor)を適用したProxyを作ってくれます.S2と異なり,SpringのコンテナはAOPに一切タッチしないため,すべてはこのProxyFactoryBeanがお膳立てをしてくれるわけです.
そのProxyFactoryBeanですが,使い勝手はいまいちです.何がいまいちかというと,Interceptor/Advisorはそれを実装したBeanの名前を指定するのですね.当然,そのBeanは別途定義する必要があります.なので,ちょっとデバッグ用にトレース出したいよ,という場合でもこうなっちゃいます.

<bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <bean class="study.Foo"/>
    </property>
    <property name="interceptorNames"><value>debugInterceptor</value></property>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

そりゃまぁ,たいした量じゃないことは分かるんですけどね.でも,Proxy本体とばらけて定義するのがやな感じ.それから,DebugInterceptorは特にプロパティもなくて簡単に使えるからいいけれど,トランザクションやセキュリティの類だと超面倒になりそう.
そんな時には,オリジナルのProxyFactoryBeanを作っちゃえ! っていうのがSpring流.らしいです.
これってもしかするとAnt風かも.Antもいろいろなタスクを標準で用意してくれていますが,それで足りなかったりビルドファイルの記述が大変になる場合はオリジナルのタスクを作ったりしますよね.makeなんかと比べると,いちいちコード書いてコンパイルして... っていう作業が多くなりがちです.なんとなく,そんな感じ.
そういう例の一つとして,TransactionProxyFactoryBeanが説明されています.これは,いわゆるコンテナ管理のトランザクションを実現するためのものです.実際には,そのためのAspect(Interceptor)としてTransactionInterceptorが用意されているのですが,これを普通にProxyFactoryBeanでWeavingしようとするととっても大変!! みたい.
そんな場合にTransactionProxyFactoryBeanを使うと,それがもう少し簡単に使えるよ! っていうことらしいんですね.
もう一つの例として,EJBがあげられています.あまり詳しくは書かれていないのですが,どうやらSimpleRemoteStatelessSessionProxyFactoryBeanとかっていうProxyFactoryBeanがあって,これを使うと普通のBean呼び出しをEJB呼び出しに変えてしまえるような,そんなAspectをWeavingしてくれるそうです.なるほどなるほど.


それでですね,ちょっと話が逸れるのですが,その辺のコードを眺めていてちょっと気づいたことがあります.こういう場合には,「AOP その10」で学習したIntroduction Adviceとか,「Seasarのからさわぎ」でまさたかさんが説明してくださったような「interfaceインスタンス化するAspect」なんていうのが有効に使えそうです.まさたかさん,宴会でそれはCrosscutting Concernとは違うのではないか,そういうのはOOPでの実装継承のようなもので,AOPの使い方を間違えているのではないか,と主張してしまいましたが,間違っていたのは私のようです.心より恥じる.


さぁ気を取り直して,そんな便利なTransactionProxyFactoryBeanを使ってみようか? なんて一瞬思ったのですが,ここでのテーマはトランザクションではありません.ちょっと便利なProxyFactoryBeanがテーマなのです.それなら,ちょっと便利なProxyFactoryBeanを作ることが日記の使命! ということで,意味はありませんがおなじみのDebugInterceptorをWeavingするProxyFactoryBeanを作ってみましょう.クラス名は当然DebugProxyFactoryBeanです.本来はDebugTraceProxyFactoryBeanにしたいところですが,Interceptorの名前がDebugInterceptorなので仕方がありません.無念だ.

それで,その作り方を知りたいのですが,この「5.6. Convenient proxy creation」にはろくな解説がありません.EJBを置き換えるとか与太話はいいからそっちの説明してくれよ.嘘です,ごめんなさい,与太話だなんて思ってません,本当です,失礼しました.m(__)m


ともかくですね,ドキュメントは参考にならなさそうです.そんな場合は,ソースを見られるのがオープンソースのいいところ.しくしくしく.しょうがねー,見るか.とりあえず,TransactionProxyFactoryBeanを参考にします.
オリジナルのProxyFactoryBeanを作るには,とりあえずProxyConfigというクラスを継承すればいいようです.名前から想像するに,Proxyを構成できるってことですよね.うんうん.ちょっと意外ですが,ProxyConfigは抽象クラスではありません.なので,派生クラスで実装しなければならないメソッドがあるわけでもなさそうです.じゃあなぜこれを実装するのか? その謎は後でProxyを作るところで発覚しました.が,説明は面倒なのでコードを見たほうが早いと思うので,いっちゃいましょう.

package study;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.support.AopUtils;
import org.springframework.aop.interceptor.DebugInterceptor;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

public class DebugProxyFactoryBean extends ProxyConfig implements FactoryBean, InitializingBean {
    private Object target;
    private Object proxy;
    //InitializingBean
    public void afterPropertiesSet() {
        if (this.target == null) {
            throw new AopConfigException("Target must be set");
        }

        DebugInterceptor interceptor = new DebugInterceptor();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addInterceptor(interceptor);
        proxyFactory.copyFrom(this);  //ここ注目
        proxyFactory.setTargetSource(new SingletonTargetSource(target));
        proxyFactory.setInterfaces(AopUtils.getAllInterfaces(this.target));
        this.proxy = proxyFactory.getProxy();
    }
    //FactoryBean
    public Object getObject() throws Exception {
        return this.proxy;
    }
    public Class getObjectType() {
        if (this.proxy != null) {
            return this.proxy.getClass();
        }
        else if (this.target != null) {
            return this.target.getClass();
        }
        else {
            return null;
        }
    }
    public boolean isSingleton() {
        return true;
    }
    //setters/getters
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
}

実際にProxyを作成するのは,afterPropertiesSet()です.この,FactoryであるところのBeanに対するプロパティの設定が終わったところでコンテナから呼び出される,InitializingBeanのメソッドですね.そこでちょっとしたエラーチェックだけ行った後,Proxyを作成しています.
Proxyを作成するには,ProxyFactoryクラスを使います.このクラス,実はProxyConfigのサブクラスです.こいつにいろいろ設定するわけですが,その一部の情報は同じくProxyConfigのサブクラスであるこいつ自身,すなわちDebugProxyFactoryBeanからコピーします.ちょっと分かりにくい感じですが,実はProxyConfigは,aopProxyFactoryoptimizefrozenといった,前回学習したプロパティを持っています.ということで,定義ファイルでこのDebugProxyFacotryBeanに設定したプロパティを,実際にProxyを作成するProxyFactoryにコピーするということなんですね.なるほどー.? なるほどか?
DebugProxyFactoryBeanProxyFactoryのサブクラスにすれば,そんなわけのわからない事をしなくても済むような気がするのですが,面倒だしまぁよしとしましょう.無念だ.
ともかくですね,ProxyFactoryを作って,DebugInterceptorを追加して,なんかそれらしいことをすればいいようです.心より恥じる.
ともかくオリジナルの,この私のオリジナルのProxyBeanFactoryが完成しました.後は使うべし.
ということで,定義ファイルを用意します.<bean>要素以下のみ.

<bean id="foo" class="study.DebugProxyFactoryBean">
    <property name="target">
        <bean class="study.Foo"/>
    </property>
</bean>

うおぉぉぉっ! これがSpringのXMLか?*2 これまでと全然違って超シンプル.やったね!
省略しますが,Fooにはprint()というメソッドがあります.実行用のクラスでは,それを呼び出します.
ということで実行!!!

Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.print()] args=[Ljava.lang.Object;@18166e5] target is of class study.Foo]
Spring must die.
Debug interceptor: next returned

\(^o^)/

*1:でも,これはSpringネガティブキャンペーンの一環だったりするのは内緒である.

*2:一応「これが俺の肺か?」なのでZaizener.