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は,プロパティの型とその型を扱うPropertyEditorMapsetCustomEditors(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()で文字列化できるクラスなら何でもオッケーってした方が良さそうですね.

本を読むのが大好きです

...嘘です*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のインスタンスを破棄するとタイミングでこのメソッドを呼び出してくれます.
それにしても,どうしてこう名前が不ぞろいなのでしょう? InitializingBeanDisposableBeanDisposableBeanというインタフェースなのにdestroy()というメソッド.いびつだ... ちなみにAvalon Frameworkでは,Initializable#initialize()Disposable#dispose()です.こっちの方が断然きれいだと思うのですが.
再度,気を取り直してお試しコーナー.
InitializingBeanDisposableBeanimplementsした簡単なクラスを用意します.

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」にはドキュメントされていないようです.って,よく考えたらAvalonECM(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を組み合わせてアプリケーションを構築したいというような場合,InitializingBeanDisposableBeanimplementsするというのはあまりうれしくないらしいですね.確かに既存のクラスを利用する場合なんかは,アダプタ(ラッパー)みたいなものを作らなきゃいけなくなったりして,いまひとつかもしれません.ということで,次はリフレクション編に進みます.

*1:再度,書きたかっただけです.

Spring Framework 入門記 Beanその7 ライフサイクル(リフレクション編)

引き続きライフサイクルです.
ごくごく普通のBeanやPOJOを組み合わせてアプリケーションを構築するとなると,その中にはSpringのことなんか知らないクラスもたくさん出てくるでしょう.あるいは,Springに依存したくないと考えることもあるでしょう.ですから,Springが提供するInitializingBeanDisposableBeanimplementsすることができなかったり適当でなかったりする場合が多々あると考えられます.しかし,それでも初期化や後片付けのタイミングは必要だったりします.
そんな時に便利なように,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のインスタンスの定義ごと*1init-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)というメソッドを持っています.つまり,実際にカスタマイズできるのはBeanFactoryextendsしたインタフェースであるConfigurableListableBeanFactoryというインタフェースをimplementsしたコンテナなんですね.ほとんどの場合,XmlBeanFactoryしか使わないと思いますが,このクラスは当然ConfigurableListableBeanFactoryimplementsしています.
さて,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>

実行用のクラスでBeanFactoryPropertyPlaceholderConfigurerを組み込みます.

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実装クラスを使ったというだけで,自分で何か作ったわけではないのがいまひとつ.
ということで,次はBeanFactoryPostProcessorimplementsしたクラスを作ろうかなぁ.まぁ,気が向けばということで.

*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(