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(