Spring Framework 入門記 Beanその9 PropertyEditorだよ
「その8」でBeanFactory
のカスタマイズができました.後は,PropertyEditor
の実装クラスを用意すれば,BigDecimal
対応はすぐにでもできそうなので,先にやってしまいます.
まずjava.beans.PropertyEditor
ですが,これまで作ったことがないので随分大変そうだなぁ,とJavaDocを見てため息が出てしまいました.ところが,Springが用意しているFileEditor
のソースを見ると,PropertyEditorSupport
を継承していて,自分で実装しなければならないメソッドは2つだけなんですね.しかもかなり単純.
こんな感じです.
package study; import java.beans.PropertyEditorSupport; import java.math.BigDecimal; public class BigDecimalEditor extends PropertyEditorSupport { public void setAsText(String text) throws IllegalArgumentException { setValue(new BigDecimal(text)); } public String getAsText() { return getValue().toString(); } }
次は,CustomEditorConfigurer
を使用してBeanFactory
に組み込まなくてはなりません.
CustomEditorConfigurer
は,プロパティの型とその型を扱うPropertyEditor
のMap
をsetCustomEditors(Map)
メソッドで設定してあげればいいようです.
ということで,BeanFactory
カスタマイズ用定義ファイルに次を追加!
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer" > <property name="customEditors"> <map> <entry key="java.math.BigDecimal"> <bean class="study.BigDecimalEditor"/> </entry> </map> </property> </bean>
今度は<map>
要素を使ってしまいました.新しいPropertyEditor
を作ったら,<entry>
要素を追加してあげればいいわけですね.
次にBigDecimal
型のプロパティを持つBeanを作成します(ソースは省略).
Beanの定義ファイルをこんな感じにして...
<bean name="foo" class="study.Foo"> <property name="number"><value>100.00</value></property> </bean>
実行すると,しっかりプロパティに値を設定することができました.
BigDecimalEditor
のコードを見ると,BigDecimal
に特化しないで,String
の引数を1つだけ持つコンストラクタがあり,toString()
で文字列化できるクラスなら何でもオッケーってした方が良さそうですね.
Lakers 104 - 103(OT) Bucks
\(^o^)/
コービーが決勝点なのかな?
これで5連勝,波に乗ってきました.シーズンも終盤で,もうキングスには届かないでしょうけれど,プレーオフに間に合ってくれてよかったです.
本を読むのが大好きです
...嘘です*1.
日記のために本を読みます.
先週読み終えた「MDA モデル駆動アーキテクチャ(ISBN:4434038133)」に続いて,今度は「MDA導入ガイド(ISBN:4844318691)」に取り組みます.
っていうか,こっちを最初にすればよかったかな.こっちの方が随分と易しそう.
*1:明日最終回のドラマの宣伝風に(「娘を愛しています... 嘘です」って感じだったような).
Spring Framework 入門記 Beanその6 ライフサイクル(インタフェース編)
BigDecimal
用のPropertyEditor
を作ろうかと思ったのですが,そのあたりは「Reference Docunent」の4章で解説されているようなので,地道に3章を続けることにします.
オブジェクトはライフサイクルを持ちます.生まれて,活動して,捨てられて... そのようなライフサイクルのターニングポイントというのは,必ずしも自身で選択できるものでもありません.志半ばで天に召されることもあります.無念だ((これが書きたかっただけ.(^^;
)).
ということで,コンテナはBeanのライフサイクルに関与することが出来ます.といっても,初期化と破棄だけなんですね.Apache AvalonのFrameworkでは,ライフサイクルの様々なイベント(インタフェース)が用意されていたのですが,BeanやPOJOを前提に考えると適用しにくいということなのでしょうか.それにしてもあっさりしすぎで,なんだか物足りなさを感じます.
気を取り直して,Spring Frameworkのライフサイクル管理,まずはインタフェースによる方法から.
初期化のタイミングを必要とするクラスのために,InitializingBean
というインタフェースが用意されています.このインタフェースには,afterPropertiesSet()
というメソッドが宣言されています.コンテナ(BeanFactory
)は,Beanのインスタンスを生成し,プロパティの設定が終わったタイミングでこのメソッドを呼び出してくれます.
同様に,破棄されるタイミングを必要とするクラスのために,DisposableBean
というインタフェースが用意されています.このインタフェースには,destroy()
というメソッドが用意されています.コンテナは,Beanのインスタンスを破棄するとタイミングでこのメソッドを呼び出してくれます.
それにしても,どうしてこう名前が不ぞろいなのでしょう? InitializingBean
とDisposableBean
,DisposableBean
というインタフェースなのにdestroy()
というメソッド.いびつだ... ちなみにAvalon Frameworkでは,Initializable#initialize()
とDisposable#dispose()
です.こっちの方が断然きれいだと思うのですが.
再度,気を取り直してお試しコーナー.
InitializingBean
とDisposableBean
をimplements
した簡単なクラスを用意します.
package study; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; public class Foo implements InitializingBean, DisposableBean { private String text; public String getText() { return text; } public void setText(String text) { this.text = text; } public void afterPropertiesSet() throws Exception { System.out.println("initialized."); } public void destroy() throws Exception { System.out.println("destroyed."); } }
そして定義ファイルを用意(<bean>
要素だけ抜粋).
<bean id="foo" class="study.Foo"> <property name="text"><value>Foo</value></property> </bean>
そして実行!
- Loading XML bean definitions from (no description) - Creating shared instance of singleton bean 'foo' initialized. foo : study.Foo@15093f1 text=Foo class=class study.Foo
むむ? "initialized."
は表示されていますが,"destroyed."
って表示がありません.なぜ?
よく考えてみれば,Beanが破棄されるタイミングっていつなのでしょう? 「3.4.1 Lifecycle interfaces」にはドキュメントされていないようです.って,よく考えたらAvalonのECM(Excalibur Component Manager)でも,コンテナをdisposeしていたことを思い出しました.Springでも,BeanFactory
を終了してあげればいいのかな.おぉ,destroySingletons()
メソッドを発見しました((でもこれ,BeanFactory
のメソッドではなくて,ConfigurableBeanFactory
のメソッドなのですね.このあたりもいずれ...)).
三度,気を取り直して入門記の「その2 setterによるプロパティの設定」で作成した実行用のクラスに以下を追加.
factory.destroySingletons();
そして実行!!
- Loading XML bean definitions from (no description) - Creating shared instance of singleton bean 'foo' initialized. foo : study.Foo@15093f1 text=Foo class=class study.Foo - Destroying singletons in factory {org.springframework.beans.factory.xml.XmlBeanFactory defining beans [foo]; Root of BeanFactory hierarchy} destroyed.
お・っ・け・ー.
destroySingletons()
というメソッド名から一目瞭然ですが,これでDisposableBean#destroy()
メソッドが呼び出されるのは,そのインスタンスがsingletonの場合だけです.prototypeの場合は呼び出されません.間違いない.
入門記の「Bean その1 id/name属性とsingleton属性」で書いたように,Springでは取得したインスタンスをコンテナに戻さないわけですから,コンテナはそのインスタンスの生涯について関与することはもうできないということですね.無念だ*1.
Spring Frameworkのようなコンテナに依存しないBeanやPOJOを組み合わせてアプリケーションを構築したいというような場合,InitializingBean
やDisposableBean
をimplements
するというのはあまりうれしくないらしいですね.確かに既存のクラスを利用する場合なんかは,アダプタ(ラッパー)みたいなものを作らなきゃいけなくなったりして,いまひとつかもしれません.ということで,次はリフレクション編に進みます.
*1:再度,書きたかっただけです.
Spring Framework 入門記 Beanその7 ライフサイクル(リフレクション編)
引き続きライフサイクルです.
ごくごく普通のBeanやPOJOを組み合わせてアプリケーションを構築するとなると,その中にはSpringのことなんか知らないクラスもたくさん出てくるでしょう.あるいは,Springに依存したくないと考えることもあるでしょう.ですから,Springが提供するInitializingBean
やDisposableBean
をimplements
することができなかったり適当でなかったりする場合が多々あると考えられます.しかし,それでも初期化や後片付けのタイミングは必要だったりします.
そんな時に便利なように,Springのコンテナが指定した任意のメソッド(とはいっても制限はありますが)を呼び出してくれるように設定することが出来ます.
初期化のタイミング(プロパティの設定後)で呼び出してほしいメソッドがあれば,それを<bean>
要素のinit-method
属性で指定します.ただし,呼び出せるのは引数のないメソッドだけです.戻り値はあってもなくても構いません.
同様に,singletonの場合に破棄のタイミングで呼び出してほしいメソッドがあれば,それを<bean>
要素のdestroy-method
属性で指定します.
例によって実験コーナー.
まずは簡単なクラスを作成.
package study; public class Bar { protected String text; public String getText() { return text; } public void setText(String text) { this.text = text; } public void initialize() { System.out.println("initialized."); } public void dispose() { System.out.println("disposed."); } }
次に定義ファイル(<bean>
要素だけ抜粋).
<bean id="bar" class="study.Bar" init-method="initialize" destroy-method="dispose"> <property name="text"><value>Bar</value></property> </bean>
そして実行!
- Loading XML bean definitions from (no description) - Creating shared instance of singleton bean 'bar' initialized. bar : study.Bar@19ce060 text=Bar class=class study.Bar disposed.
ばっちり!
init-method
属性およびdestroy-method
属性を使うと,BeanをSpringに依存させずに済みます.しかし,その場合はBeanのインスタンスの定義ごと*1にinit-method
属性やdestroy-method
属性を指定しなければなりません.ちょっと間違いやすいような気がします.インタフェースを実装しておけば,確実にコンテナがそのメソッドを呼び出してくれるのですから,その方が安心なんですけどね.どちらがいいのか,悩みどころです.
*1:prototypeの場合は雛形(?)ごと.
Spring Framework 入門記 Beanその8 BeanFactoryのカスタマイズ
IoC − 制御の逆転.ということで,ここまでは一方的にコンテナ側から呼び出されることばかり見てきましたが,世の中やっぱり逆転できないこともあります(たぶん).そんな時に備えて,Beanの側からコンテナを利用することもできます.
コンテナを利用するには,コンテナを手に入れなくてはなりません.そんな場合は,BeanFactoryAware
インタフェースをimplements
します.すると,そのメソッドsetBeanFactory(BeanFactory)
を通じてコンテナを入手することが出来ます.この部分はDependency Injectionなのですね.
Bean定義XMLの中で,自分がどんな名前で定義されたのかを知りたい場面もあるかもしれません(本当か?).そんな場合は,BeanNameAware
インタフェースをimplements
します.すると,そのメソッドsetBeanName(String)
メソッドを通じて自分の名前を入手することが出来ます.なぜ必要なんだろう?
ま,自分の名前はおまけ程度ということで忘れてもよさげな気がします.よし,しばし忘れましょう.
次はFactoryBean
のことが紹介されていたりするのですが,ちょっと毛色が違う気がするのでこれもしばし忘れましょう.
さて,コンテナを手に入れるとどんな素敵なことが出来るのでしょうか?
getBean(String)
getBean(String, Class)
isSingleton(String)
getAliases(String)
containsBean(String)
... 素敵ですか?
ま,コンテナもおまけ程度ということで忘れてもよさげな気がします.よし,しばし忘れましょう.
次は,「3.6 Customizing the BeanFactory」.って,BeanFactory
忘れちゃダメじゃん.
BeanFactory
をカスタマイズするには,BeanFactoryPostProcessor
というインタフェースをimplements
します.このインタフェースは,postProcessBeanFactory(ConfigurableListableBeanFactory)
というメソッドを持っています.つまり,実際にカスタマイズできるのはBeanFactory
をextends
したインタフェースであるConfigurableListableBeanFactory
というインタフェースをimplements
したコンテナなんですね.ほとんどの場合,XmlBeanFactory
しか使わないと思いますが,このクラスは当然ConfigurableListableBeanFactory
をimplements
しています.
さて,BeanFactoryPostProcessor
を使うとどんなことができるのでしょうか.Springに用意されているBeanFactoryPostProcessor
実装クラスには,次のものがあるようです.
CustomEditorConfigurer
PropertyEditor
を組み込みます.PropertyOverrideConfigurer
- プロパティリストの記述に従い,Beanのプロパティ値をオーバーライドします.
PropertyPlaceholderConfigurer
- 定義ファイル中に
${var}
のように記述された(一種の)変数をプロパティリストの記述に従い置換します. PreferencesPlaceholderConfigurer
PropertyPlaceholderConfigurer
と同様ですが,プロパティリストではなくJ2SE1.4のプリファレンスAPIを使用します.
うん,こっちのほうが素敵(キラッ).あるじゃないですか,CustomEditorConfigurer
.これですよ,探していたのは.
というわけで,いつものように実験ですが,せっかく見つかったCustomEditorConfigurer
は先延ばしにして,ここではPropertyPlaceholderConfigurer
で遊んでみます.
まずは定義ファイルに変数を埋め込んで...
<bean name="foo" class="study.Foo"> <property name="text"><value>${foo}</value></property> </bean>
実行用のクラスでBeanFactory
にPropertyPlaceholderConfigurer
を組み込みます.
package study; import java.io.FileInputStream; import java.util.Iterator; import java.util.Map; import java.util.Properties; import org.apache.commons.beanutils.BeanUtils; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.xml.XmlBeanFactory; public class Main { public static void main(String args) { try { XmlBeanFactory factory = new XmlBeanFactory(new FileInputStream("beans.xml")); Properties params = new Properties(); params.put("foo", "FOO"); PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setProperties(params); configurer.postProcessBeanFactory(factory); String names = factory.getBeanDefinitionNames(); for (int i = 0; i < names.length; ++i) { Object bean = factory.getBean(names[i]); System.out.println(names[i] + " : " + bean); Map props = BeanUtils.describe(bean); for (Iterator it = props.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); System.out.println("\t" + entry.getKey() + "=" + entry.getValue()); } } factory.destroySingletons(); } catch (Exception e) { e.printStackTrace(); } } }
実行します.
- Loading XML bean definitions from (no description) - Creating shared instance of singleton bean 'foo' initialized. foo : study.Foo@11671b2 text=FOO class=class study.Foo - Destroying singletons in factory {org.springframework.beans.factory.xml.XmlBeanFactory defining beans [foo]; Root of BeanFactory hierarchy} destroyed.
Fooオブジェクトのtextプロパティが確かに置き換わっています.一応は成功です.
しかし... これが素敵とは言い難いですね.プロパティリストの内容はファイルとして外に出せばよいとしても,いろいろなBeanFactoryPostProcessor
の組み込みをいちいちプログラミングしたくもありません.よく見ると,PropertyPlaceholderConfigurer
もBeanっぽく使えそうな感じがします.ということで,こいつを定義ファイル化しましょう.
といっても,通常のBean定義とは別にしておいた方が使いまわしが楽そうな気がします.ということで,BeanFactory
のカスタマイズ用定義ファイル作成!
<?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="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > <property name="properties"> <props> <prop key="foo">FOO</prop> </props> </property> </bean> </beans>
初登場の<props>
を使ってしまいました.「3.8.3 Bean definitions specified in XML (XmlBeanFactory)」を先取りです.
次にこれを読み込んで,BeanFactory
をカスタマイズするわけですが,このカスタマイズ用定義ファイルを読み込むBeanFactory
とこれまでのBean定義ファイルを読み込むBeanFactory
の関係をどうしたものか? このあたりの学習はまだなので,とりあえず独立させておきます(手抜きモード).
package study; import java.io.FileInputStream; import java.util.Iterator; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.xml.XmlBeanFactory; public class Main { public static void main(String args) { try { XmlBeanFactory factory = new XmlBeanFactory(new FileInputStream("beans.xml")); //BeanFactoryのカスタマイズ XmlBeanFactory configurerFactory = new XmlBeanFactory(new FileInputStream("factory.xml")); String configurers = configurerFactory.getBeanDefinitionNames(); for (int i = 0; i < configurers.length; ++i) { Object bean = configurerFactory.getBean(configurers[i]); if (bean instanceof BeanFactoryPostProcessor) { //postProcessするのはbeans.xmlを読み込んだBeanFactory *1; } } factory.destroySingletons(); } catch (Exception e) { e.printStackTrace(); } } }
これで実行!!
- Loading XML bean definitions from (no description) - Loading XML bean definitions from (no description) - Creating shared instance of singleton bean 'propertyPlaceholderConfigurer' - Creating shared instance of singleton bean 'foo' initialized. foo : study.Foo@18b81e3 text=FOO class=class study.Foo - Destroying singletons in factory {org.springframework.beans.factory.xml.XmlBeanFactory defining beans [foo]; Root of BeanFactory hierarchy} destroyed.
大・成・功!
これで,新たにBeanFactoryPostProcessor
実装クラスをコンテナに組み込みたくなった場合でも,定義ファイルに書き加えてやるだけで済みます.とはいえ,既存のBeanFactoryPostProcessor
実装クラスを使ったというだけで,自分で何か作ったわけではないのがいまひとつ.
ということで,次はBeanFactoryPostProcessor
をimplements
したクラスを作ろうかなぁ.まぁ,気が向けばということで.
*1:BeanFactoryPostProcessor) bean).postProcessBeanFactory(factory); } } //Beanの表示 String[] names = factory.getBeanDefinitionNames(); for (int i = 0; i < names.length; ++i) { Object bean = factory.getBean(names[i]); System.out.println(names[i] + " : " + bean); Map props = BeanUtils.describe(bean); for (Iterator it = props.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); System.out.println("\t" + entry.getKey() + "=" + entry.getValue(