Spring Framework 入門記 Contextその3 イベント

今回は「3.10.2. Propagating events」です.
ApplicationContextはイベントを通知してくれるそうです.このイベントはApplicationEventです.ということは当然,このイベントを受け取るにはApplicationListenerimplementsするわけですね.
では,このApplicationEventがどんなイベントなのかというと,タイムスタンプを持つだけの超単純な代物なんですね.これは,汎用のイベントメカニズムのためのものらしいです.つまり,アプリは必要に応じてApplicationEventextendsして独自のイベントを作成していいよってことみたい.
ということは,アプリからイベントが発生したことを通知できなきゃいけないわけですが,そのためにApplicationContextにはpublishEvent(ApplicationEvent)というメソッドが用意されています.こいつを呼び出すと,ApplicationContextはリスナーにイベントを配信してくれるようです.
ちなみに,Spring Framework自身が用意しているイベントもあります.

ContextRefreshedEvent
ApplicationContextが初期化されたり,リフレッシュ(!)されたことを通知します.
ContextClosedEvent
ApplicationContextがクローズされたことを通知します.
RequestHandledEvent
Spring FrameworkDispatcherServletが使用するもので,HTTPリクエストが処理されたことを通知します.

何気にこんなところでリフレッシュとか出てくるし.もっとまともに取り上げて解説してくれてもいいと思うんですけど.重要な機能ですよねぇ?
まぁ,いずれ解説があることに期待しつつ,なければ実験するってことで気を取り直していきましょう.
イベントを受け取るにはApplicationListenerimplementsすればいいわけですが,ApplicationContextにはaddApplicationListenerなんてメソッドがあるわけではありません.ApplicationContext君はApplicationListenerimplementsしているBean君には,片っ端から通知してくれるようです.ちょっと乱暴者?
だいたいのことは分かったので,さっそく実験.
まずはイベントリスナーを作ります.

package study;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;

public class Zaizen implements ApplicationListener {
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
            System.out.println("無念だ.");
        }
    }
}

ここでは,ContextClosedEventだけを処理しています.
次にBeanの定義ファイル.プロパティも何も無いので超簡単です(<bean>要素のみ抜粋).

    <bean name="zaizen" class="study.Zaizen">
    </bean>

最後に実行用のクラス.これも単純にApplicationContextを作ってclose()するだけです.

package study;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {
    public static void main(String args) {
        try {
            FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                    new String { "factory.xml", "beans.xml" });
            context.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

これを実行すると...

無念だ.

とちゃんと出力されました.このクラス,なにもしていないのですからさぞかし無念でしょう.
ところで,この例ではzaizen教授はsingletonです(<bean>要素で明示していないため,デフォルトでそうなります).果たしてprototypeではどうなるのでしょう? ちょっと疑問.
そんな場合は実験です.定義ファイルでprototypeを指定します.

    <bean name="zaizen" class="study.Zaizen" singleton="false">
    </bean>

これで実行すると...

無念だ.

同じですねー.ちょっと意外.イベントは届かなくなるんじゃないかと予想したんですが.今の場合,ApplicationContextからは何もgetBean()していないので,close()する前にgetBean()してみます.

            context.getBean("zaizen");

これで実行しても,結果は同じでした.getBean()をいくら増やしても同じ,きっちり1回だけイベントが通知されます.調べてみると,イベントを受け取るインスタンスgetBean()で返ってきたどのインスタンスとも別であることが分かりました.どうやら,prototypeの雛型になっているインスタンスに対して通知されるようです.ちなみにsingletonの場合は,最初からインスタンスが1つしかないので,getBean()で返ってくるのと同じインスタンスに通知されます.覚えておきましょう(それが難しい!).
大体こんなところでしょうか.独自のイベントを作ることもちょっとだけ考えましたが,そんなに使う機会があるとも思えないし,そういうこともできるってことを忘れないようにする(これが難しい!)ってことでこの節を終わりにします.


ところですっかり忘れていましたが,かつてJavaBeansといえばマイクロソフトのVBX(古い?)やOCXに対応するGUIのためのコンポーネント(ウィジェット)というイメージだった時期がありましたよね.その当時JavaBeansといえばプロパティ共々イベントが必ず解説されていて,JavaBeansに対応したGUI構築ツール(ポトリペタペタ系)の中にはイベントをグリグリッと配線できたものもありました.VisualAgeとか.
でも,Spring FrameworkはイベントソースであるBeanとイベントリスナーであるBeanをつないでくれたりはしないんですね.サーバサイドだとスレッドやトランザクションなどが絡んできて,GUIまわりに比べてObserverパターンを使いにくいということが一因でしょうか.あるいはAspectがその代替手段として使えるからでしょうか.Springは,PropertyEditorなどJavaBeansを徹底活用の印象があっただけに,ちょっと意外な感じがしました.