BeanFactoryPostProcessorとPropertyPlaceholderConfigurer(Spring1.2.1)

BeanFactoryPostProcessorは,BeanFactoryをインスタンス化した後に,そのインスタンスを引数にとり,何らかの処理をするものである.アプリケーションの起動後の初期化パターンとして使えることがあると思う.BeanFactoryPostProcessorの一つ,PropertyPlaceholderConfigurerは,設定ファイルであるbeans.xmlでの設定値を更に外部プロパティファイルへと移動することが可能な機能を持つらしいので,BeanFacotryPostProcesssorの挙動確認を兼ねて試してみた.
BeanFactoryPostProcessorの組み込み
PropertyPlaceholderConfigurer
サンプルドライバ

BeanFactoryPostProcessorの組み込み

BeanFactoryPostProcessorは,インタフェースであり,以下のメソッドを宣言している.
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
ここで,アプリケーションにBeanFactoryPostProcessorを組み込むというのは,以下の作業を指す.
BeanFactoryPostProcessor具象クラスの作成. あるいはSpringFrameworkにある上記具象クラスの選定. 本頁のサンプルでは,あるいはSpringFrameworkにあるPropertyPlaceholderConfigurerを使用する. この詳細は後述.
BeanFactoryクラスの生成.
上で作成あるいは生成したBeanFactoryPostProcessorクラスの生成.
BeanFactoryPostProcessorのpostProcessBeanFactoryを呼び出す. 引数は先に生成したBeanFactoryオブジェクトである.
postProcessBeanFactoryメソッド内で何をするかは,各アプリケーション毎の要件による. で,上の手作業でハードコードする手順だと,BeanFactoryPostProcessorはBeanFactory生成後処理の,統一的な仕組みを提供するということ以外にあまり意味はない. ところが,BeanFactoryを拡張したApplicationContextは,上記の作業のいくつかを自動的に行ってくれる. すなわち読み込んだbeans.xmlにBeanFactoryPostProcessorの具象クラスがあればそれを検出,生成し,該当beans.xmlを読み込んだBeanFactoryを引数としてpostProcessBeanFactoryを実行してくれる. BeanFactoryPostProcessor自身もbeans.xmlに記述することで,柔軟に初期化処理のモジュール化やDIが可能となる.

PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurerは,beans.xmlに設定シンボルを記述し,そのシンボルに別プロパティファイルのプロパティ値を埋め込む. 以下はPropertyPlaceholderConfigurerを使用する本頁のサンプルのbeans.xmlである.

${…}で記述された部分は,今までならそのまま文字列としてbeanにsetされていたが,PropertyPlaceholderConfigurerの働きにより,プロパティファイルの値に置き換えられる. 例えば,プロパティファイルに
sampleBean.value1=HOGE
とあれば,beans.xmlの
${sampleBean.value1}
という記述は
HOGE
と置き換えられて処理される. この置き換えのタイミングは,PropertyPlaceholderConfigurer#postProcessBeanFactory呼び出し時である.

サンプルドライバ

以下にサンプルのドライバクラスを示す.

package org.fireproject.springsample;

import java.net.URL;
import java.io.IOException;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;

import org.apache.log4j.Logger;

public class HelloBeanFactory {
    /** ログ出力用. */
    private static Logger logger = Logger.getRootLogger();

    public static void main (String args[]) throws IOException {
	BeanFactory factory = null;

	/*
	 * 起動パラメータに"userApplicationContext"があれば,
	 *  ApplicationContextを使用する.
	 */
	if (args.length == 0
	    || !args[0].equals("useApplicationContext")) {
	    factory = getBeanFactory();
	} else {
	    factory = getApplicationContext();
	}

	String[] beanKeys = new String[]{
	    "sampleBean",
	};

	// beanの生成
	for (int i = 0; i < beanKeys.length; i++) {
	    Object bean = factory.getBean(beanKeys[i]);
	    logger.info(bean);
	}
    }

    /**
     * beanFactoryを生成して返す.
     * BeanFactoryPostProcessorの生成と呼び出しをハードコードする.
     *
     * @return BeanFactoryPostProcessorで初期化したBeanFactoryオブジェクト.
     */
    private static BeanFactory getBeanFactory() {
	logger.info("getBeanFactory start");
        // ClassLoaderがうまくbeans.xmlを見付けられない場合や,
        // jarファイル内にbeans.xmlがある場合は,以下のようにする.
	URL url = HelloBeanFactory.class.getResource("beans.xml");
	Resource res = new UrlResource(url);
 	XmlBeanFactory factory = new XmlBeanFactory(res);

	PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
	cfg.setLocation(new FileSystemResource("./conf/springsample.conf"));
	cfg.postProcessBeanFactory(factory);

	return factory;
    }

    /**
     * ApplicationContextを生成して返す.
     * beanFactoryを生成し,そこからApplicationContextを生成する.
     * ApplicationContextの機能により,
     * BeanFactoryPostProcessorの生成とそのpostProcessBeanFactory呼び出しによる
     * BeanFactoryの初期化は自動的に行われる.
     *
     * @return ApplicationContextオブジェクト
     */
    private static BeanFactory getApplicationContext() {
	logger.info("getApplicationContext start");
        // ClassLoaderがうまくbeans.xmlを見付けられない場合や,
        // jarファイル内にbeans.xmlがある場合は,以下のようにする.
	URL url = HelloBeanFactory.class.getResource("beans.xml");
	Resource res = new UrlResource(url);
 	XmlBeanFactory factory = new XmlBeanFactory(res);

	GenericApplicationContext ctx = new GenericApplicationContext(factory);
	ctx.refresh();
	return ctx;
    }
}

BeanPostProcessorのハードコードによる生成と使用は,getBeanFactoryメソッドにて行っている.
XmlBeanFactory factory = new XmlBeanFactory(res);
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource(“./conf/springsample.conf”));
cfg.postProcessBeanFactory(factory);
BeanFactoryを生成し,BeanFactoryPostProcessorの具象クラスであるPropertyPlaceholderConfigurerをインスタンス化,設定しpostProcessBeanFactoryを呼び出している. ApplicationContextを使用すると,これと同様の処理は以下となる.
XmlBeanFactory factory = new XmlBeanFactory(res);
GenericApplicationContext ctx = new GenericApplicationContext(factory);
ctx.refresh();
今回は,beans.xmlにはSampleBeanFactoryPostProcessorを記述した.

package org.fireproject.springsample;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.FileSystemResource;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import org.apache.log4j.Logger;

public class SampleBeanFactoryPostProcessor extends PropertyPlaceholderConfigurer {

    /** $B%m%0=PNOMQ(B. */
    private static Logger logger = Logger.getRootLogger();

    public void setLocation(String param) {
	logger.info("setLocation of " + this.getClass().getName());
	super.setLocation(new FileSystemResource(param));
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	logger.info("postProcessBeanFactory of " + this.getClass().getName());
	logger.info("beanFactory = " + beanFactory.getClass().getName());
	super.postProcessBeanFactory(beanFactory);
    }
}
これは,各メソッドの呼び出しタイミングを知るためにログ出力をしている程度で,それ以外は直接PropertyPlaceholderConfigurerを使用するのと変わらない. ApplicationContextは,beans.xmlにBeanPostProcessorの具象クラスであるSampleBeanFactoryPostProcessorが記述されているので,これを自動検知し,インスタンス化し,postProcessBeanFactoryを実行する. ハードコードする方法と,ApplicationContextを使用する方法では,行数は1行しか違わないが,プロパティファイルを外部化している点はもちろん,BeanFactoryPostProcessorの生成,呼び出しそのものを設定ファイル化している点で,ApplicationContextを使用した方法の方がより柔軟で実装も楽ある. 最後にサンプルを実行した際のログを示す. まずハードコード版.
$> java -jar springsample.jar 
springsample.HelloBeanFactory:52 - getBeanFactory start
xml.XmlBeanDefinitionReader:132 - Loading XML bean definitions from URL
  [jar:file:/home/matsu/springsample.jar
   !/org/fireproject/springsample/beans.xml]
config.PropertyResourceConfigurer:154 - Loading properties from file
  [/home/matsu/./conf/springsample.conf]
support.AbstractBeanFactory:219
  - Creating shared instance of singleton bean 'sampleBean'
springsample.SampleBean:12 - setValue1: param = HOGE
springsample.SampleBean:23 - setValue2: param = FUGA
springsample.SampleBean:34 - setValue3: param = FOO
springsample.SampleBean:45 - setValue4: param = BAR
springsample.SampleBean:56 - setValue5: param = 100
springsample.HelloBeanFactory:41
  - org.fireproject.springsample.SampleBean
  [valu1 = HOGE / valu2 = FUGA / valu3 = FOO / valu4 = BAR / valu5 = 100]
次にApplicationContext版.
$> java -jar springsample.jar useApplicationContext 
springsample.HelloBeanFactory:76 - getApplicationContext start
xml.XmlBeanDefinitionReader:132 - Loading XML bean definitions from URL
  [jar:file:/home/matsu/springsample.jar
  !/org/fireproject/springsample/beans.xml]
support.AbstractApplicationContext:289
  - 2 beans defined in application context
  [org.springframework.context.support.GenericApplicationContext;hashCode=20233936]
support.AbstractBeanFactory:219
  - Creating shared instance of singleton bean 'sampleBeanFactoryPostProcessor'
springsample.SampleBeanFactoryPostProcessor:15
  - setLocation of org.fireproject.springsample.SampleBeanFactoryPostProcessor
springsample.SampleBeanFactoryPostProcessor:20
  - postProcessBeanFactory of org.fireproject.springsample.SampleBeanFactoryPostProcessor
springsample.SampleBeanFactoryPostProcessor:21
  - beanFactory = org.springframework.beans.factory.xml.XmlBeanFactory
config.PropertyResourceConfigurer:154
  - Loading properties from file
  [/home/matsu/./conf/springsample2.conf]
...省略...
support.DefaultListableBeanFactory:262 - Pre-instantiating singletons in factory
  [org.springframework.beans.factory.xml.XmlBeanFactory defining beans
   [sampleBean,sampleBeanFactoryPostProcessor];
   root of BeanFactory hierarchy]
support.AbstractBeanFactory:219 - Creating shared instance of singleton bean 'sampleBean'
springsample.SampleBean:12 - setValue1: param = HOGE2
springsample.SampleBean:23 - setValue2: param = FUGA2
springsample.SampleBean:34 - setValue3: param = FOO2
springsample.SampleBean:45 - setValue4: param = BAR2
springsample.SampleBean:56 - setValue5: param = 200
springsample.HelloBeanFactory:41
  - org.fireproject.springsample.SampleBean
  [valu1 = HOGE2 / valu2 = FUGA2 / valu3 = FOO2 / valu4 = BAR2 / valu5 = 200]

This article was written by Fujiko