Spring Framework 入門記 AOPその11 ProxyFactoryBean再訪
順番だと次は「5.4. Advisors in Spring」なのですが,とても短いので軽ーく.Advisorは,Aspectをモジュール化するもので,たいていはAdviceとPointcutを組み合わせたものです.だそうです.直訳風です.以上!
ということで「5.5. Using the ProxyFactoryBean to create AOP proxies」へ進みます.苦しんだ「AOPその1」でこの連載^H^H日記を救ってくれたのがこの5.5節でした.ようやく戻ってきましたよ.
さて,すでに学習したとおり,ProxyFactoryBean
はSpringのAOP機能を実現しているFactoryBean
であり,このAOP編で疑いなく最も重要な存在です.たぶん.
ProxyFactoryBean
もSpringのコンテナから見るとただのBeanですから,プロパティを設定することができます.ProxyFactoryBean
は,次のプロパティを持っています.これまでの学習で,次のプロパティを使用してきました.
target
- AspectをWeavingされるBeanを指定します.
interceptorNames
- Advice(Interceptor)やAdvisorであるBeanの名前を指定します.
それ以外にも,ProxyFactoryBean
は,次のプロパティを持っています.
proxyTargetClass
boolean
のプロパティで,true
にするとinterface
ではなく,class
をターゲットにします.これは,java.lang.reflect.Proxy
ではなく,CGLIBを使ってProxyを作成することを意味します.Proxyをターゲットクラスとして扱うことができます.java.lang.reflect.Proxy
を使用したProxyの場合,それをターゲットクラスとして扱うことはできません(ターゲットクラスのインスタンスではない).interface
を通してのみ利用できます.optimize
boolean
のプロパティで,true
を設定すると積極的に最適化されたProxyを作成するとのことです.よく分かっていなければ使うなとのこと.僕はいい子なので使わないことにします.無念だ.frozen
boolean
のプロパティで,true
に設定すると,ProxyFactoryBean
が構成された後(コンテナにDependency Injectionされた後)は,ProxyFactoryBean
を変更しないことを意味します.デフォルトはfalse
です.
通常は,XMLの定義ファイルに書かれた通りにProxyFactoryBean
を設定した後,プログラマティックに設定を更新するようなことはあまりないと思います.よって,たいていはtrue
に設定してもいいのではないかと思います.exposeProxy
boolean
のプロパティで,true
に設定すると,ProxyをThreadLocal
から取得できるようになるとのこと.そのThreadLocal
は,AopContext
から取得できるそうです.って「5.5. Using the ProxyFactoryBean to create AOP proxies」には書いてあるのですが,現実にはAopContext#currentProxy()
で取得するのかな? このドキュメント,結構実装と食い違っている記述が多いんで,油断大敵です.
それで,AopContext
は抽象クラスなのですが,そのインスタンスはどこから入手できるのでしょう? うーん,もっと先に出てくるのかな? とりあえず保留.無念だ.aopProxyFactory
ProxyFactoryBean
が使用するProxyのファクトリであるProxyFactory
を設定します.これを切り替えることで,まったく異なったAOP Proxyを使用できるようになるというわけです.ふーん.あまり必要になって欲しくない感じですが,万が一の時にはいろいろ好きなようにできるということですね.proxyInterfaces
Class
の配列のプロパティで,Proxyが実装するinterface
を指定します.このプロパティが設定されない場合は,CGLIBを使用してターゲットの派生クラスとしてProxyが作成されます.
このプロパティが設定されると,java.lang.reflect.Proxy
を使用したDynamic Proxyが作成されます.singleton
FactoryBean
おなじみのboolean
のプロパティです.false
に設定されると,コンテナからインスタンスを取得するたびに新たなProxyが作成されます.ステートフルにmixinを組み込む場合に使えるとか.でもそれってどんな場合?
ソースを見ると他にもプロパティらしきものがあるようなのですが,ドキュメントに登場するのは以上です.
これまでの学習では,proxyInterface
を指定したことがなく,常にCGLIBによるProxyを使ってきたので,これを試してみることにしましょう*1.
まずはinterface
を用意します.
package study; public interface Foo { void print(); }
その実装クラスを作成します.
package study; public class FooImpl implements Foo { private String text; public String getText() { return text; } public void setText(String text) { this.text = text; } public void print() { System.out.println(text); } }
そして定義ファイル.<bean>
要素以下のみです.
<bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="study.FooImpl"> <property name="text"><value>FOO</value></property> </bean> </property> <property name="proxyInterfaces"><list><value>study.Foo</value></list></property> <property name="interceptorNames"><value>debugInterceptor</value></property> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> </bean>
ごらんのとおり,proxyInterfaces
プロパティにFoo
を指定しています.
そして実行用のクラスです.中ほどのみ.
Foo foo = (Foo) context.getBean("foo"); foo.print(); FooImpl fooImpl = (FooImpl) foo;
まずはinterface
で取り出して,print()
メソッドを呼び出しています.その後,FooImpl
にキャストしています.
さぁ,実行!!
Debug interceptor: count=1 invocation=[Invocation: method=[public abstract void study.Foo.print()] args=null] target is of class study.FooImpl] FOO Debug interceptor: next returned java.lang.ClassCastException at study.Main.main(Main.java:16)
というわけで,ちゃんとAspectが適用されていること,Foo
というinterface
では扱えるものの,FooImpl
というターゲット本来のclass
としては扱えないことが分かります.
なお,proxyInterfaces
プロパティを指定していても,proxyTargetClass
プロパティにtrue
を設定すると,FooImpl
にキャストすることが出来ました.CGLIBによるProxyが作成されるからですね.
なお,CGLIBを使ったProxyの場合,ターゲットでfinal
宣言されているメソッドにはAspectを適用できないそうです.なるほど.こういった点を踏まえて,両者を使い分けようということですね.この入門記のようなお遊びではともかく,実際の開発で使うなら,きっちりinterface
を定義してやりたいところです.その場合はjava.lang.reflect.Proxy
を使うことができるので,その方がよさそうな気がします.
ということで,全然たいした事をしていませんが,今日はここまで.次は「5.6. Convenient proxy creation」に進みます.