Spring Framework 入門記 Contextその4 リソース

今回は「3.10.3. Using resources within Spring」です.早く3章終わらせたいよっ! AOP(5章)に行きたいよっ!
ここでいうリソースは,URLで示されるものやファイルなどのことで,おおざっぱに言えばjava.io.InputStreamで内容を読み込むことができるようなものです(たぶん).java.lang.ClassLoader#getResource(String)でいうところのリソースと同じようなものですね.
リソースを手に入れるには,ApplicationContext#getResource(String)を使います.このメソッドの引数をlocationと呼びます.ソース上でそうなっているので.それでですね,そのlocationの解釈は,ApplicationContextの実装によって異なるみたいです.ちなみにFileSystemXmlApplicationContext*1の場合は次のようになっているようです.

  1. location"classpath:"で始まっている場合,その後続の文字列は(先頭が'/'で始まっている場合はそれを取り除いて)クラスパス上のリソースとして扱われます.
  2. locationをURLとして解釈できれば(MalformedURLExceptionがスローされなければ),そのURで示されるリソースとして扱われます.
  3. それ以外はファイルシステム上のリソースとして扱われます.ただし,locationの先頭が'/'で始まっている場合は取り除かれます.

という具合です.最後のやつは,WEBアプリケーションで都合がいいようにってことらしいです.
ApplicationContext#getResource(String)の戻り値は,Resourceというインタフェースです.これは,次のメソッドを持っています.

  • getInputStream()
  • getURL()
  • getFile()
  • exists()
  • isOpen()
  • getDescription()

どんなリソースでも,全てのメソッドが有効というわけではありません."file:"でないURLで示されるリソースでは,getFile()は例外を吹っ飛ばしてくれます.
こんな感じでリソースを手に入れて,InputStreamを通して内容を読み込むことができるのですが,これはやっぱりApplicationContextの機能なんですよね.またApplicationContextに依存するのか? なんて思いますよね.その程度のことでApplicationContextAwareimplementsしなきゃいけないのか? なんて思いますよね.
ご安心ください.そんなあなた(誰?)のために,ResourceEditorをご用意させていただきました*2.え? それは何だって? それはですね,PropertyEditorの実装なんですねー.覚えてますか,PropertyEditor.そう,文字列をプロパティの型に変換する,あのプロパティエディタです.BigDecimalなプロパティに値を設定するために使った,あのプロパティエディタです(遠くを見る目).こいつを使うと,Resourceなプロパティにsetterで値を設定できちゃうんですねー.もちろん,コンストラクタにも使えるでしょうとも.
あれ? でも結局,Resourceには依存しちゃうのね.Springとは無関係なBeanとして使えるわけではないのですね.それくらいだったら,素直にjava.io.Filejava.net.URLのプロパティを持つようにした方がいい感じがするなぁ.あ,でもそれだとクラスパスから取ってくることができないのか.うー,なんか今ひとつ.しょうがない,java.io.InputStreamのプロパティで手を打つことにしようか.
なお,ResourceEditorApplicationContextを使用していません.(^^; 実は,getResource(String)は,ResourceLoaderのメソッドで,ApplicationContextはそれをextendsしているんですね.それでResourceEditorが使用するResourceLoaderの実装は,location引数の解釈がFileSystemXmlApplicationContextとは微妙に異なり,ファイルシステムを見てくれません.なんだかなー,今はApplicationContextのお勉強中なのにぃ.
いずれにせよ,あんまり嬉しい代物じゃない気がしてしょうがないんですが,逃げちゃダメですか? そうですかダメですか.無念だ.
やむを得ません,お試ししましょう.
今はApplicationContextの学習中なので,とりあえずは素直にApplicationContextAwareなクラスを作ってみます.

package study;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class ResourceChecker implements ApplicationContextAware {
    private ApplicationContext context;
    private String location;
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }
    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
    public boolean isExists() {
        return context.getResource(location).exists();
    }
}

こいつは,指定されたりソースが存在するかどうかを示すbooleanのread onlyなプロパティを持っています.面倒なので内容を読み込むようなことはしていません.心より恥じる.
次にBean定義のXMLファイル.いつも通り<bean>要素のみの抜粋.

    <bean id="file" class="study.ResourceChecker">
        <property name="location"><value>beans.xml</value></property>
    </bean>
    <bean id="url" class="study.ResourceChecker">
        <property name="location"><value>http://java.sun.com/
    </bean>
    <bean id="classpath" class="study.ResourceChecker">
        <property name="location"><value>classpath:study/Main.class</value></property>
    </bean>
    <bean id="notfound" class="study.ResourceChecker">
        <property name="location"><value>notfound</value></property>
    </bean>

locationを何パターンかご用意させていただきました.
これを入門記の「Contextその1」で作成した実行用のクラスで実行します.

file : study.ResourceChecker@13e58d4
    class=class study.ResourceChecker
    exists=true
    location=beans.xml
url : study.ResourceChecker@39e5b5
    class=class study.ResourceChecker
    exists=true
    location=http://java.sun.com/
classpath : study.ResourceChecker@32efa7
    class=class study.ResourceChecker
    exists=false
    location=classpath:study/Main.class
notfound : study.ResourceChecker@13c7378
    class=class study.ResourceChecker
    exists=false
    location=notfound

こんなもんでいっか.あまり楽しいものではなかったですね.ま,必要になったら思い出すということで.
さぁ,3章も残りはあと少し! 頑張るぞぉ.

*1:だーかーらー,長いんだよー.

*2:もちろん用意したのは私ではありません.

お仕事スタイル

天気回復で久しぶりにこのコーナー.

このコーディネートも今シーズンはこれで最後.ニットとパンツはこの週末にレジュイールに持っていこう.

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を徹底活用の印象があっただけに,ちょっと意外な感じがしました.