Spring Framework 入門記 Beanその10 FactoryBean

今回は,昨日の学習中に「毛色が違う」とすっ飛ばしてしまった「3.4.3 FactoryBean」について学習します.
FactoryBeanというのは,自身がFactoryであるようなBeanということです.言い換えると,Beanとして扱うことのできるFactoryですね.このようなものがなぜ必要なのかというと,インスタンスの生成をコンテナにやってもらうのではなく,その外で用意して,それをDependency Injectionの輪に加えたい場合でしょう.
FactoryBeanインタフェースには,次の3つのメソッドが宣言されています.

Object getObject()
オブジェクトを返します.
Class getObjectType()
このFactoryBeanが返すオブジェクトの型を返します.
boolean isSingleton()
このFactoryBeanが返すオブジェクトがsingletonならtrueを返します.

簡単そうですね.
それでは例として,ThreadLocalで保持しているスレッド固有の情報をSpringにDependency Injectionしてもらうサンプルを作ってみます.とはいってもサンプルなので,ちょっと手抜きしてスレッド固有の情報はただの文字列ということにします.
まずはFactoryBeanの実装です.これ自身はSpringで扱われる普通のBeanですから,プロパティを持つことができます.ということで,ThreadLocalをプロパティとして,そこからget()したオブジェクト(今回はString)を自分のgetObject()の戻り値になるようにします.この文字列はそのときの状況で変わりうるので,Singletonではありません.
ということで,こんな感じ.

package study;
import org.springframework.beans.factory.FactoryBean;

public class ThreadLocalString implements FactoryBean {
    ThreadLocal threadLocal;
    public ThreadLocal getThreadLocal() {
        return threadLocal;
    }
    public void setThreadLocal(ThreadLocal local) {
        threadLocal = local;
    }
    public Object getObject() throws Exception {
        return threadLocal.get();
    }
    public Class getObjectType() {
        return String.class;
    }
    public boolean isSingleton() {
        return false;
    }
}

では,これを使用した定義ファイルを作成します(<bean>要素のみ抜粋).

    <bean id="threadLocal" class="java.lang.ThreadLocal">
    </bean>
    <bean id="threadLocalString" class="study.ThreadLocalString">
        <property name="threadLocal"><ref bean="threadLocal"/></property>
    </bean>
    <bean id="foo" class="study.Foo" singleton="false">
        <property name="text"><ref bean="threadLocalString"/></property>
    </bean>

ここでのポイントは,fooという名前を持ったBeanのtextプロパティはコンテナが設定してくれるのですが,その値(文字列)はthreadLocalStringというFactoryBeanが返す値だということです.
最後に実行用のクラスを作ります.

package study;
import java.io.FileInputStream;
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"));
            ThreadLocal threadLocal = (ThreadLocal) factory.getBean("threadLocal");

            threadLocal.set("Midori");
            Foo foo = (Foo) factory.getBean("foo");
            System.out.println(foo.getText());

            threadLocal.set("Saeko");
            foo = (Foo) factory.getBean("foo");
            System.out.println(foo.getText());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

この実行用クラスでは,fooを取得する前に,ThreadLocalに文字列を設定しています.そのため,取得したfootextプロパティには,その時それぞれの文字列が設定されているはずです.
それでは実行します.

 - Loading XML bean definitions from (no description)
 - Creating shared instance of singleton bean 'threadLocal'
 - Creating shared instance of singleton bean 'threadLocalString'
 Midori
 Saeko

いいねぇ*1
という感じなんですが,惜しいことにFactoryBeanが返したオブジェクトはDependency Injectionしてもらえないんですよね.無念だ.
大して難しい話じゃないと思うのですが,現状はDependency Injectionした後に,そのオブジェクトがたまたまFactoryBeanだったらgetObject()するという流れになっているようで,そこで再度Dependency Injectionするようにはなっていないのですね.DTD的にも,FactoryBeanのプロパティとそれが返すオブジェクトのプロパティを区別する書き方はできませんし,しょうがないとあきらめましょう.ただ... 無念だ.

*1:遠い昔のアコムのCM(宇宙人なやつ)風