Spring Framework 入門記 Beanその4 depends-on,autowire,dependency-check
コンテナの動きを調整する<bean>
要素の属性がいくつかあります.
depends-on
属性
depends-on
属性は,Beanの初期化順に依存関係がある場合に使用します.constructor-basedではあまり問題にならないと思いますが,setter-basedでBeanの参照を設定する場合には,初期化順の依存関係が問題になる場合が考えられます.例えばAとBという2つのBeanがあり,AはBへの参照をプロパティに持っているとします.つまり,AはsetB(B)
というsetterを持つわけですが,このsetterの中で,Bのプロパティにアクセスしたり,メソッドを呼び出したりするかもしれません.ということは,AのsetB(B)
が呼び出されたときには,Bのプロパティの設定は完了していないといけないわけです.このような場合,AはBに依存していることを,depends-on
属性で明示します.
depends-on
で指定する値は,<ref>
要素のbean
属性と同じく,Beanの名前です(「Spring Framework 入門記 Beanその1 id
/name
属性とsingleton
属性」を参照).
Apache AvalonのECM(Excalibur Compoenent Manager)ではコンポーネントの初期化順を制御できなかったので(たぶん),これはありがたいです.
autowire
属性
setter-basedでもconstructor-basedでも,他のBeanへの参照を設定することができます.これまでは,参照するBeanを<ref>
属性で明示的に指定していたわけですが,これをコンテナにお任せすることができます.これをautowiringと呼びます.このためにautowire
属性を使用することができます.
autowire
属性には,次の値を指定することができます.
no
- デフォルトです.autowiringは行いません.
byName
- Beanの名前でautowiringを行います.
byType
- Beanの型でautowiringを行います.
byName
を指定すると,そのBeanのプロパティ名と一致する名前を持つBeanがあれば,それをプロパティに設定してくれます.
byType
を指定すると,そのBeanのプロパティの型と一致するBeanが(一つだけ)あれば,それをプロパティに設定してくれます.ただし,プロパティの型と一致するBeanが複数あると,またしても例外が吹っ飛んできます.
一見便利そうなautowiringですが,個人的にはあまり魅力を感じません.ないよりはあってもいい,くらいの感じです.というのも,ある程度Beanの数が増えてくると,byName
・byType
とも使えないことになると思うからです.byName
は,参照する側のBeanのインスタンスが複数あり,それぞれのプロパティに異なった値を設定する場合に使えなくなります.byType
は,参照される側のBeanのインスタンスが複数ある場合に使えなくなります.ちょっとしたサンプル程度なら楽ができるとは思うのですが,その程度だったら明示的に指定してもたいした手間がかかるわけではないわけですから,結局大きなメリットにはならないと思うわけです.
dependency-check
属性
属性名からはピンときませんが,この属性により,Beanのプロパティの設定漏れをコンテナにチェックしてもらうことができます.Dependency Injectionのdependencyということで全く正しいのだと思いますが,なんだかピンとこないんですよね.
dependency-check
属性には,次の値を指定することができます.
none
- プロパティの設定漏れをチェックしません.
simple
- プリミティブ型および
java.lang.String
型のプロパティに設定漏れがないかチェックします. objects
java.lang.String
以外のオブジェクト(参照)型のプロパティに設定漏れがないかチェックします.default
- デフォルトです.
none
と同様,設定漏れをチェックしません.
「Spring Framework Reference Documentation」の「Checking for dependencies」ではobjects
ではなくobject
となっていますが,DTDではobjects
となっています.また,simple
はプリミティブ型に加えてコレクション型のプロパティをチェックするとなっていますが,1.0RC2ではコレクション型はチェックされず,代わりに(?)java.lang.String
型のプロパティがチェックされます.このあたりの説明は,DTD中のコメントの方が実装と一致しているようです.
dependency-check
もありがたいようで,実はそれほどでもない感じです.プロパティに値や参照を設定していないことをチェックするということは,それらのプロパティにデフォルト値がないために,明示的に設定することを強制したいということだと思います.それはよくある状況です.しかし,そのような状況はクラス単位で出てくる話だと思うのですね.ですが,dependency-check
属性は<bean>
要素の属性です.つまり,インスタンス単位で設定します.とあるインスタンスの設定(<bean>
要素)で,dependency-check
属性の指定が漏れてしまったら,全然意味がありません.このようなチェックが必要であれば,そのクラスのコンストラクタで強制するのが適当だと思います.
今回は,コンテナの動作を調整する<bean>
要素の3つの属性について学習しました.
現時点ではdepends-on
属性以外はあまり使う価値を感じませんが,経験を深めていくと考えが変わるかどうか... 将来のお楽しみということで.
次はライフサイクルにするかBigDecimal
を扱うPropertyEditor
を作るか.明日の気分で決めたいと思います.
Spring Framework 入門記 Beanその5 setter-basedとconstructor-basedの混在
ちょっと追加.
setter-basedとconstructor-basedは,一つのBean定義の中で混在することができます.
例のPersonを例として...
<?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="koichik" class="study.Person"> <constructor-arg index="0" type="java.lang.String"><value>koichik</value></constructor-arg> <constructor-arg index="1" type="java.net.URL"><value>http://d.hatena.ne.jp/koichik/</value></constructor-arg> <property name="friend"><ref bean="makotan"/></property> </bean> </beans>
とすると,コンストラクタでname
とdiary
が設定され,その後setterメソッドを通じてfriend
が設定されます.
sectionモジュール
なんだか日記が妙に長くて,自分が書いたネタを探すのに苦労してしまいます.
ということで,sectionモジュールというものを導入してみました.
どうにか見た目などを整えて,これはなかなかと思いきや,一部リンクがうまくいってないような?
どうやら,一度に複数の小見出しを作って更新すると,その小見出しは同じ名前(<a>
要素のname
属性)を持ってしまうようです.確かにそういうことを何度かやってしまったなぁ.
ということで,今後は見出し単位で更新するよう注意しよう.
お買い物スタイル
先週購入したYSLのパンツを取りに新宿へ.
- 黒のジップアップブルゾン(DIOR HOMME 04SS)
- 黒とグレイのボーダーニット(BURBERRY Prosum 03SS)
- 黒のコーティング・デニム(JIL SANDER 03-04AW)
- 黒のショートブーツ(JIL SANDER 03-04AW)
昨秋購入したJIL SANDERのコーティング・デニム,遅ればせながらお出かけデビュー.
先週購入したDIOR HOMMEのブルゾンは早速デビュー.なかなか相性よさげです.このブルゾンは,DIOR HOMMEにしてはシンプルというか,余計なものが付いていなくて,エディ本来のミニマルさが強く感じられて大ヒットです.
新宿伊勢丹のGUCCIにて
雑誌で見かけて取り寄せをお願いしていた携帯ストラップが入荷していたので購入しました.
GUCCIらしく,鈍く(黒っぽく?)輝く棒状のシルバー(7mm×75mm)がついた携帯ストラップ.すごくシンプルなのに存在感があって,まさにTOM FORD FOR GUCCI.
しかしながら,自分の使っているプラスチッキーな携帯に合わせる気がしません.スタイリッシュな携帯といえばauのINFOBARが話題になっていましたが,深澤さんのデザインしたものっておもちゃっぽい(安っぽいという意味ではなく,玩具っぽいっていうか)感じがあまり好きではないんですよね.かっこいい携帯ないかなぁ.いっそ,トム・フォードがデザインした携帯があれば... Tom Ford for DoCoMo by SONYみたいな.
Malaysian GP決勝 M.Schumacher 優勝
\(^o^)/
開幕戦と異なり接戦だったようですが,きっちり開幕から連続ポール・トゥ・ウィン,さすがです.
琢磨はこの週末ずっと波に乗れなかったようで,残念です.バーレーンで頑張ってほしいですね.
Spring Framework 入門記 Beanその2 setterによるプロパティの設定
「3.3. Properties, collaborators, autowiring and dependency checking」を読みながら,プロパティの設定について学習します.
Spring FrameworkでBeanのプロパティを設定するには,setter-basedとconstructor-basedがあります.setter-basedでは,Beanのプロパティをsetterメソッドを通じてコンテナに設定してもらいます.constructor-basedでは,Beanのプロパティをコンストラクタを通じてコンテナに設定してもらいます.
まずは,setter-basedから始めます.
setterメソッドを通じてBeanのプロパティを設定するには,<bean>
の子要素として<property>
要素を記述します.
<bean name="foo" class="study.Foo"> <property name="bar">〜</property> </bean>
<property>
要素の内容にはいろいろ記述できるようですが,まずは<value>
要素による値の設定について.
<value>
要素を記述すると,コンテナはその内容文字列をBeanのプロパティに設定してくれます.
<property name="text"><value>Spring Framework</value></property>
「はじめてのSpring Framework」では,value
要素が冗長だなぁ,と書いたのですが,「3.3. Properties, collaborators, autowiring and dependency checking」の中に<value>
要素を記述しないで<property>
要素の内容に直接,値を記述している例がありました.なーんだ,省略できるんじゃん,と思ったのですが,実際にやってみたらバリデーションではねられました.(;_;)
DTD的には省略不可になっているので,単なるドキュメントの間違いのようです.まぁ,そういうもんだってことであきらめましょう.
コンテナは,<value>
要素の内容文字列をBeanのプロパティの型に変換して設定してくれます.どのような型がサポートされているかというと,コンテナが直接サポートしているらしきものは次の型です.
int
などのプリミティブ型.java.lang.String
型.
ずいぶんあっさりしています.それ以外の型は,java.beans.PropertyEditor
を利用するのだそうです.ふーん.JavaBeansなんだからそれが真っ当といえばそうなのかなぁ.しかし,GUIコンポーネント以外では無縁な代物だと思いこんでいました,PropertyEditor
.
なお,次のPropertyEditor
実装クラスが標準でコンテナに組み込まれています(「4.3.2 Built-in PropertyEditors
, Converting types」より).
ClassEditor
- 内容文字列を完全限定名とする
java.lang.Class
オブジェクトにしてくれます. FileEditor
- 内容文字列をパスとする
java.io.File
オブジェクトにしてくれます. LocaleEditor
- 内容文字列を
ja_JP
のような言語コード等とするjava.util.Locale
オブジェクトにしてくれます. PropertiesEditor
- 内容文字列を読み込んだ
java.util.Propertie
オブジェクトにしてくれます. StringArrayPropertyEditor
- 内容文字列をコンマで区切った
java.lang.String
の配列にしてくれます. URLEditor
- 内容文字列をURLとする
java.net.URL
オブジェクトにしてくれます.
あれー,java.math.BigDecimal
はサポートしてくれてないのですね.それは悲しい.と一瞬思いましたが,学習テーマ*1として好都合なので,近日中に挑戦します.
ここまでの分を簡単に確認してみることにします.まずはBeanを作ります.
package study; import java.net.URL; public class Person { private String name; private URL diary; //以下,上記フィールドのsetter/getterが続く }
そして定義ファイルを用意します.
<?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="koichik" class="study.Person"> <property name="name"><value>Koichi Kobayashi</value></property> <property name="diary"><value>http://d.hatena.ne.jp/koichik/</value></property> </bean> </beans>
実行用のクラスを作って
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.xml.XmlBeanFactory; public class Main { public static void main(String args) throws Exception { XmlBeanFactory factory = new XmlBeanFactory(new FileInputStream("beans.xml")); 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()); } } } }
実行すると,
koichik : study.Person@9e5c73 diary=http://d.hatena.ne.jp/koichik/ class=class study.Person name=Koichi Kobayashi
となり,URL
型を含むプロパティの値が設定できていることが確認できました((class
"プロパティ"が出力されているのはご愛敬ということで.ちょっと便利そうだしネ.)).
次は,他のBeanへの参照を設定する<ref>
要素です.これがIoC/Dependency Injectionの名前の由来になっている機能ですよね,きっと.
使い方は,<ref>
要素のbean
属性で参照するBeanの名前を指定するだけです.この名前は,前回いろいろ試した<bean>
要素のid
属性またはname
属性で指定されたものです.
ということで,先のPerson
クラスにstudy.Person
型のfriend
というプロパティを追加して,定義ファイルを次のように変えてみます(makotan,ごめん).
<?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="koichik" class="study.Person"> <property name="name"><value>Koichi Kobayashi</value></property> <property name="diary"><value>http://d.hatena.ne.jp/koichik/</value></property> <property name="friend"><ref bean="makotan"/></property> </bean> <bean id="makotan" class="study.Person"> <property name="name"><value>makotan</value></property> <property name="diary"><value>http://d.hatena.ne.jp/makotan/</value></property> <property name="friend"><ref bean="koichik"/></property> </bean> </beans>
これを実行すると,
koichik : study.Person@9e5c73 diary=http://d.hatena.ne.jp/koichik/ class=class study.Person friend=study.Person@b25b9d name=Koichi Kobayashi makotan : study.Person@b25b9d diary=http://d.hatena.ne.jp/makotan/ class=class study.Person friend=study.Person@9e5c73 name=makotan
となり,無事に関連付けができたことを確認できました.相互参照していても問題なしですね.
なお,<ref>
要素でbean
属性の代わりにlocal
属性を指定すると,Beanのid
だけで参照するようになります(name
属性は参照されません).この場合,local
属性が参照するid
を持つBeanが定義ファイル中に記述されていないと,XMLのバリデーションでエラーになります.local
属性はIDREF型なのですね.XMLエディタを使う場合には,編集時にチェックできるのでよさげです.
なぜlocal
という名前なのかと思いきや,Springのコンテナは階層化することができるらしく,bean
属性で指定した場合は,現在のコンテナに該当のBeanが見つからなければ,親のコンテナから探してくれるとのことです.それがlocal
属性の場合には,一つのXMLファイル(すなわち一つのコンテナ)中にあるBeanを参照するだけですから,local
という名前なんですね(おそらく).コンテナの階層化についても近々学習せねば.
それから,プロパティの値にnull
を設定するには,<null>
要素を使用します.
<property name="name"><null/></property>
次のように書くと,null
ではなく,空文字列になってしまいます.
<property name="name"><value></value></property>
プロパティの型が空文字列から変換できる値を持たない場合(int
など)の場合,例外がビッシビシ(死語)飛んでくるので注意しましょう.
次は,constructor-basedを学習します.
*1:日記のネタとも言います
Spring Framework 入門記 Beanその3 constructorによるインスタンス生成
setter-basedでBeanのプロパティに値や参照を設定する方法が分かったので,次はconstructor-basedに進みます.
コンテナが扱うオブジェクトが全てJavaBeansの約束事に従う,すなわちデフォルトコンストラクタを持ち,プロパティを設定することで状態を変更できるのであれば,setter-basedだけでも十分なのかもしれません.しかし,世の中にはJavaBeansではないものも多々あります.そのようなものには,コンストラクタに引数を与えてインスタンスを生成しなければならないものもあります.典型的なものとして不変オブジェクトをあげることができますね.通常不変オブジェクトは,コンストラクタに渡された引数で初期化されると,それ以降は状態を変更することができません.ですから,setter-basedではうまく扱えないわけです.ということで,constructor-basedも必要になります.
constructor-basedも使い方は簡単で,<property>
要素の代わりに<constructor-arg>
要素を使って引数の値や参照を指定すればよいだけです.内容には<property>
要素と同じように,<value>
要素や<ref>
要素,<null>
要素を記述することができます*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="number" class="java.lang.Integer"> <constructor-arg><value>100</value></constructor-arg> </bean> </beans>
これを前回作成した実行用クラスで実行してみると,
number : 100 class=class java.lang.Integer
となりました.プリミティブ型でも使えますね.
ちなみに,
<bean id="string" class="java.lang.String"> <constructor-arg><value>Hoge Hoge</value></constructor-arg> </bean>
なんてやっても動きました.意味は全くありませんが.
Javaのコンストラクタには名前がなく*2,引数の数と型の並びでしか区別されません.このため,<constructor-arg>
要素には順序と型を指定することができます.順序は,index
属性で,型はtype
属性で指定します.
ということでお試しコーナー.
setter-basedの学習で使ったPersonクラスに次の2つのコンストラクタを追加します.
public Person(String name, String url) throws MalformedURLException { this.name = name; this.diary = new URL(url); } public Person(String name, URL diary) { this.name = name; this.diary = diary; }
そして,定義ファイルを以下のようにします.
<?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="koichik" class="study.Person"> <constructor-arg><value>koichik</value></constructor-arg> <constructor-arg><value>http://d.hatena.ne.jp/koichik/</value></constructor-arg> </bean> </beans>
ここではまだ,type
属性を指定していないため,どちらのコンストラクタが呼び出されるかは明示的ではありません.どうなるのか興味津々で実行してみると...
org.springframework.beans.FatalBeanException: Could not instantiate class [study.Person]; constructor threw exception; nested exception is java.net.MalformedURLException: no protocol: koichik
ありゃりゃ? なぜか最初のコンストラクタの第2引数(diary
)に最初の<constructor-arg>
要素の値である"koichik"
が渡されてしまったようです.なぜ?
気を取り直して,まずはindex
属性を指定します.
<constructor-arg index="0"><value>koichik</value></constructor-arg> <constructor-arg index="1"><value>http://d.hatena.ne.jp/koichik/</value></constructor-arg>
これはうまく動きましたが,呼び出されたコンストラクタは,Person(String, URL)
でした.なぜだろう? やはり曖昧なのはよくないということでしょうか.
次に,type
属性に変更します.
<constructor-arg type="java.lang.String"><value>koichik</value></constructor-arg> <constructor-arg type="java.net.URL"><value>http://d.hatena.ne.jp/koichik/</value></constructor-arg>
これは当然Person(String, URL)
が呼び出されます.しかし,Person(String, URL)
とPerson(URL, String)
がある場合には,どちらが呼び出されるか曖昧になってしまいます.このような場合には,index
属性とtype
属性の両方を指定すべきでしょう.
なお,type
属性には完全限定名を指定する必要があります.油断してtype="String"
などと記述すると例外がぶっ飛んでくるので気をつけましょう.
次は,コンテナの動きを調整するパラメータについて学習します.