Spring Framework 入門記 AOPその6 Interception Around Advice

今回からAdviceを学習します.
SpringのAdviceは,ライフサイクルとタイプで分類することができます.
ライフサイクルには,次のものがあります.

per-class
全てのターゲットオブジェクトを一つのインスタンスで処理するものです.
per-instance
ターゲットオブジェクトのインスタンスごとにAdviceのインスタンスを用意するものです.

per-instanceは,ターゲットオブジェクトにインスタンス変数を追加するようなイメージのAdviceの場合に使用します.
タイプには次のものがあります.

Interception Around advice
メソッドJoinpointを包み込むように適用されるAdviceです.
Before advice
メソッドJoinpointの実行前に適用されるAdviceです.
Throws advice
メソッドJoinpointが例外をスローした場合に適用されるAdviceです.
After Returning advice
メソッドJoinpointが例外をスローすることなくリターンした場合に適用されるAdviceです.
Introduction advice
ターゲットにinterfaceを追加するAdviceです.

今回は,Interception Around adviceを学習します.
Springでは,いわゆるAround AdviceをInterceptorと呼んでいます.これは,Springでそう決めているのではなくて,AOP Allianceの定義しているinterfaceをそのまま使っているようです.では,なぜAOP AllianceではInterceptorというのかと思い,ちょろっと調べてみました... よく分かりませんでした.無念だ.
AOP Allianceの定義しているinterfaceを見ると,AdviceInterceptorは別々に用意されています.推測ですが,AOPの概念としてのAdviceと,それを実現するメカニズムの一つであるInterceptorを区別したかったのかな,と思いました.OOPでいうと特化と継承の違いみたいな.
用語の由来はともかく,SpringでAround Adviceを実装するには,AOP AllianceのinterfaceであるMethodInterceptorを実装します.これはInterceptorextendsしたinterfaceで,invoke(MethodInvocation)というメソッドが一つだけ宣言されています.
MethodInvocationもまたAOP Allianceが定義しているinterfaceで,Joinpointを表しています(1つのJoinpointに複数のAdviceが適用される場合は,内側のAdviceを表現する場合もあります).Interception Around adviceでターゲットオブジェクトのメソッドを実行するには,このMethodInvocationproceed()メソッドを呼び出します.その前後で好きな処理を行ったり,場合によってはproceed()を呼び出さずにすませたり,好きなようにすればいいわけですね.
ということで,サクッとお試ししましょう.簡単そうなネタとして,「AspectCookBookのRecipesより」で紹介したAsynchronousRoutingRecipeをやってみましょう.任意のメソッドを別スレッドで実行するというAdviceです.任意とはいっても,実質的には戻り値のない(voidな)メソッドでないと意図した結果にならない場合もありそうですが.

package study;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AsyncInterceptor implements MethodInterceptor {
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Thread thread = new Thread() {
            public void run() {
                try {
                    invocation.proceed();
                }
                catch (Throwable ignore) {
                }
            }
        };
        thread.start();
        return null;
    }
}

Threadのサブクラスを匿名クラスとして作って,そこでMethodInvocation#proceed()を呼び出しています.そのために,invocation引数はfinal宣言しています.
んで,Aspectを適用する対象クラスです.

package study;

public class Foo {
    private String text;
    public void slowPrint() {
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ignore) {
        }
        System.out.println("のそのそ");
    }
}

こやつのslowPrint()は,1秒間居眠りしてからメッセージを出力します.
次に定義ファイル.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <bean class="study.Foo"/>
        </property>
        <property name="interceptorNames"><value>interceptor</value></property>
    </bean>

    <bean id="interceptor" class="study.AsyncInterceptor">
    </bean>
</beans>

今回は,ターゲットになるBeanの定義をProxyBeanFactoryの定義の中に埋め込んでみました.少しだけすっきり?
また,手抜きしてPointcutを指定していないので,ターゲット(Foo)の全てのメソッドにAdviceが適用されます.本来なら,voidなメソッドだけが対象となるようにPointcutを指定する方が安全でしょう.
で,実行用のクラス.

package study;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;

public class Main {
    public static void main(String[] args) {
        try {
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance();
            BeanFactoryReference ref = locator.useBeanFactory("context");
            ApplicationContext context = (ApplicationContext) ref.getFactory();

            Foo foo = (Foo) context.getBean("foo");
            foo.slowPrint();
            System.out.println("てきぱき");

            ref.release();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ここでは,Foo#slowPrint()を呼び出した後にメッセージを出力しています.Adviceが適用されていなければFoo#slowPrint()が出力するメッセージの後に出力されるはずですが...
実行!!!

てきぱき
のそのそ

おっけー.

お出かけスタイル

Seasarのからわさぎ」が今週だと思って早起きしてしまいました.無念だ.
ひがさんがコメント付けてくれているのでまだ出かけなくて大丈夫なのかな? なんて思って確認して来週だと気づいた次第.心より恥じる.
悔しいので「倉俣史朗に会いに青山へ」行ってきます.
ついでに文化村の「マルモッタン美術館展モネ,ルノワール印象派展」も行ってこようかな.

ネストしたAspect

ひがさんの日記のまねをしてみました.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="dateTarget" class="java.util.Date">
        <constructor-arg type="long"><value>0</value></constructor-arg>
    </bean>

    <bean id="date" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target"><ref bean="dateTarget"/></property>
        <property name="interceptorNames"><value>advisor</value></property>
    </bean>

    <bean id="advisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="mappedNames">
            <list>
                <value>hashCode</value>
                <value>getTime</value>
            </list>
        </property>
        <property name="advice"><ref bean="interceptor"/></property>
    </bean>

    <bean id="interceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
    </bean>
</beans>

この時点でいやになりますね.S2に比べてなんて煩雑なんだろう.
ともかく,これでDate#hashCode()を呼び出すと...

Debug interceptor: count=1 invocation=[Invocation: method=[public int java.util.Date.hashCode()] args=[Ljava.lang.Object;@b7b3f9] target is of class java.util.Date]
Debug interceptor: next returned

ということで,S2と違ってネストした(hashCode()から呼び出された)getTime()のトレースは出力されません.無念だ.
このようになるのは,SpringのAOPは本当にProxyとして実装されているからです.AspectのWeavingを行うProxyFactoryBeanは,ターゲットオブジェクトはいじらずに,Aspectを適用したProxyを作成してターゲットをラップするだけなのです.
AOPその3」でも書いたように,SpringのProxyはCGLIBによるものだけでなく,java.lang.reflect.Proxyによるものも用意されています.両者で同じような実装にするには,こうするしかなかったのだと想像されます.

あのSpringのロジックだと(たぶん)デフォルトコンストラクタのない クラスにはAspectを適用できないんじゃないかなぁと思います。

試してみたところ,確かにデフォルトコンストラクタがないと例外が吹っ飛んできました.(;_;) ただし,java.lang.reflect.Proxyを使ったProxyの場合は,デフォルトコンストラクタがなくても大丈夫でした.しかし,こちらの場合はProxyで使用するinterfaceを指定する必要があります.Spring的には,interface中心の設計を推奨しているようですから,これでもいいということでしょうか?
それにしても,一見同じような昨日な機能を持っていると思われるIoCコンテナAOP機能も,実装によって随分と差があることが分かって面白いですね.

PROJECT-1 KURAMATA SHIRO

行ってきました,青山へ.久しぶりに倉俣史朗の世界に触れてきました.
中でも印象に残っているのはやっぱりこれら.

How High the Moon
エキスパンドメタルのソファ.A-POC青山に展示.
Glass Chair
ガラスの板を接着剤で貼り付けた椅子.フロムファーストに展示.
Miss Blanche
アクリルの中に薔薇を浮かべた椅子.フロムファーストに展示.

いずれも素材は全く異なるのに,同じ世界が表現されていると感じます.儚さというか...
ガラス板がはがれてパラパラと崩れていきそうな"Glass Chair".見ていると焦点が定まらなくなって見えなくなりそうな"How High the Moon".薔薇だけが残されて,そして散っていきそうな"Miss Blanche".永遠とかを感じない,今一瞬しか存在しないような,そんな世界がそこにありました.
"Miss Branche"と同じく薔薇の造花をアクリルに埋め込んだブロック「薔薇の封印」が販売されていました.そんなつもりはなかったのに,予約してしまいました.やっぱり見てしまうと欲しくなってしまいます.入荷したら取りに行くわけで,その時もう一度倉俣作品を見ることができるのが今から楽しみです.
あー,手持ちの「水素の夢」と「SAMBA-M」,久しぶりに眺めようかな.

「モネ,ルノワールと印象派展」

青山へ行ったついでに渋谷へ移動してBunkamuraザ・ミュージアムへ行ってきました.
すっかり勘違いしていたのですが*1,「マルモッタン美術館展」はBunkamuraザ・ミュージアムではなくて東京都美術館だったのですね.しかももう終わってるし... こっちの方が見たかったな.1992年に池袋の三越美術館で行われた「マルモッタン美術館所蔵 モネと印象派展」で見た青い「睡蓮」は忘れることができません.あれをもう一度見たかったのに... 無念だ.いいもんね,いずれマルモッタンへ行ってやるぅ.
ともかく,せっかく渋谷まで行ったので,「モネ,ルノワール印象派展」を見てきました.こちらにも睡蓮はありました.うーん,以前見たことあるような? もっとも,モネの「睡蓮」はものすごくたくさんあるので,似た印象のものも結構あるのだと思います.
特に印象に残ったのは,やはりモネの「アルジャントゥイユの鉄橋」.ルノワールが同じ場所を描いたものも近くに展示されていて,二人の個性の違いが感じられて面白かったです.ルノワールの方はしっとりした感じなのに対して,モネの方はもっと明るいというか日差しの強さを感じました.特に水面に写り込んだ空や反射した光の描写がとても印象的でした.本当,モネの絵の水面ってしびれるなぁ.
それにしても,バブルの頃は印象派のイベントはものすごい人混みだったのですが,今時は空いているとは言えないものの,大混雑ということはなくて,見やすかったです.よかったよかった.

*1:今日はからさわぎといい,勘違い発覚の連続でした

「Delphynium Blue 青の花展」

文化村の後は渋谷西武へ.Yves Saint Laurent Rive Gaucheに仲良くしてくれている店員さんがいるので久しぶりに顔を出しに行きました.そこでは特にこれといったものはなかったのですが,渋谷西武全体に薄紫をした花が.なんですか,フラワーアレンジメントとかいうやつ? の,もっと大きいの? なんだか分かりませんが,ともかくその色に目を引かれてそこら中で立ち止まってしまいました.これがデルフィニウムという花ですか.こんな風にじっくり見るのは初めてだったのですが,薄い水色から濃い青紫まで,様々なブルーが目を楽しませてくれました.そう,これはまるでマルモッタン美術館のあの睡蓮のよう!
帰ってきてからタイトルにリンクしたページを見たのですが,イベントは3月で終わっていたんですね.期間中に行っていれば,もっとたくさん展示があったのかなあ? 無念だ.

Lakers 97 - 86 Supersonics

\(^o^)/
11連勝.もはや日記ネタにするのがバカバカしいくらい勝ち続けてます.
うーん,インテルみたいにだめぽな方が日記的には書きやすいのにな.(^^;
日記のことはともかく,このままファイナル制覇まで突っ走って欲しいものです.みんな,けがしないでくれよ!