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
は,aopProxyFactory
やoptimize
,frozen
といった,前回学習したプロパティを持っています.ということで,定義ファイルでこのDebugProxyFacotryBean
に設定したプロパティを,実際にProxyを作成するProxyFactory
にコピーするということなんですね.なるほどー.? なるほどか?
DebugProxyFactoryBean
をProxyFactory
のサブクラスにすれば,そんなわけのわからない事をしなくても済むような気がするのですが,面倒だしまぁよしとしましょう.無念だ.
ともかくですね,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.