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」に進みます.

*1:いやその,ひがさんの日記で指摘されていた「デフォルトコンストラクタがないとAspectを適用できない」を確認したときに,こっそり試したことがあるんですが,日記としてはやっていないので... 心より恥じる.