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"などと記述すると例外がぶっ飛んでくるので気をつけましょう.
次は,コンテナの動きを調整するパラメータについて学習します.

*1:DTD的には他にもいくつかあるらしいのですが,それはまた今後ということで...

*2:クラス名と同じ名前を持ちますが,多重定義されたコンストラクタ各を区別するユニークな名前はありません.