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のプロパティの型に変換して設定してくれます.どのような型がサポートされているかというと,コンテナが直接サポートしているらしきものは次の型です.

  1. intなどのプリミティブ型.
  2. 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:日記のネタとも言います