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の場合は次のようになっているようです.
location
が"classpath:"
で始まっている場合,その後続の文字列は(先頭が'/'
で始まっている場合はそれを取り除いて)クラスパス上のリソースとして扱われます.location
をURLとして解釈できれば(MalformedURLException
がスローされなければ),そのURで示されるリソースとして扱われます.- それ以外はファイルシステム上のリソースとして扱われます.ただし,
location
の先頭が'/'
で始まっている場合は取り除かれます.
という具合です.最後のやつは,WEBアプリケーションで都合がいいようにってことらしいです.
ApplicationContext#getResource(String)
の戻り値は,Resource
というインタフェースです.これは,次のメソッドを持っています.
getInputStream()
getURL()
getFile()
exists()
isOpen()
getDescription()
どんなリソースでも,全てのメソッドが有効というわけではありません."file:"
でないURLで示されるリソースでは,getFile()
は例外を吹っ飛ばしてくれます.
こんな感じでリソースを手に入れて,InputStream
を通して内容を読み込むことができるのですが,これはやっぱりApplicationContext
の機能なんですよね.またApplicationContext
に依存するのか? なんて思いますよね.その程度のことでApplicationContextAware
をimplements
しなきゃいけないのか? なんて思いますよね.
ご安心ください.そんなあなた(誰?)のために,ResourceEditor
をご用意させていただきました*2.え? それは何だって? それはですね,PropertyEditor
の実装なんですねー.覚えてますか,PropertyEditor
.そう,文字列をプロパティの型に変換する,あのプロパティエディタです.BigDecimal
なプロパティに値を設定するために使った,あのプロパティエディタです(遠くを見る目).こいつを使うと,Resource
なプロパティにsetterで値を設定できちゃうんですねー.もちろん,コンストラクタにも使えるでしょうとも.
あれ? でも結局,Resource
には依存しちゃうのね.Springとは無関係なBeanとして使えるわけではないのですね.それくらいだったら,素直にjava.io.File
やjava.net.URL
のプロパティを持つようにした方がいい感じがするなぁ.あ,でもそれだとクラスパスから取ってくることができないのか.うー,なんか今ひとつ.しょうがない,java.io.InputStream
のプロパティで手を打つことにしようか.
なお,ResourceEditor
はApplicationContext
を使用していません.(^^;
実は,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章も残りはあと少し! 頑張るぞぉ.
お仕事スタイル
天気回復で久しぶりにこのコーナー.
- 黒いカシミアシルクのジャケット(JIL SANDER 02-03AW)
- グレイでカシミアのタートルネック・セーター(JIL SANDER 02-03AW)
- 黒のウール・パンツ(JIL SANDER 02-03AW)
- 黒いスエードのチャッカ・ブーツ(Yanko)
このコーディネートも今シーズンはこれで最後.ニットとパンツはこの週末にレジュイールに持っていこう.
Spring Framework 入門記 Contextその3 イベント
今回は「3.10.2. Propagating events」です.
ApplicationContext
はイベントを通知してくれるそうです.このイベントはApplicationEvent
です.ということは当然,このイベントを受け取るにはApplicationListener
をimplements
するわけですね.
では,このApplicationEvent
がどんなイベントなのかというと,タイムスタンプを持つだけの超単純な代物なんですね.これは,汎用のイベントメカニズムのためのものらしいです.つまり,アプリは必要に応じてApplicationEvent
をextends
して独自のイベントを作成していいよってことみたい.
ということは,アプリからイベントが発生したことを通知できなきゃいけないわけですが,そのためにApplicationContext
にはpublishEvent(ApplicationEvent)
というメソッドが用意されています.こいつを呼び出すと,ApplicationContext
はリスナーにイベントを配信してくれるようです.
ちなみに,Spring Framework自身が用意しているイベントもあります.
ContextRefreshedEvent
ApplicationContext
が初期化されたり,リフレッシュ(!)されたことを通知します.ContextClosedEvent
ApplicationContext
がクローズされたことを通知します.RequestHandledEvent
- Spring Frameworkの
DispatcherServlet
が使用するもので,HTTPリクエストが処理されたことを通知します.
何気にこんなところでリフレッシュとか出てくるし.もっとまともに取り上げて解説してくれてもいいと思うんですけど.重要な機能ですよねぇ?
まぁ,いずれ解説があることに期待しつつ,なければ実験するってことで気を取り直していきましょう.
イベントを受け取るにはApplicationListener
をimplements
すればいいわけですが,ApplicationContext
にはaddApplicationListener
なんてメソッドがあるわけではありません.ApplicationContext
君はApplicationListener
をimplements
している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を徹底活用の印象があっただけに,ちょっと意外な感じがしました.