Spring Framework 入門記 Contextその5 メソッド呼び出し

やばいです.3章を勢いで終わらせないと,モチベーションが低下してしまいます.刺激不足です.早く5章に達しなくては!幸い,3章の残りはすでに先取りしてしまったものも含まれていて,分量は多くありません.ということで,少し駆け足でいきましょう.
3.11. Extra marker interfaces and lifecycle features」では,ContextパッケージはBeansパッケージ上に構築されているため,BeansFactory同様,InitializingBeanインタフェース/<init-method>要素,DisposableBeanインタフェース/<destroy-method>要素を使うことができるということが説明されています.これらは「Beanその6」および「Beanその7」で学習しました.また,ApplicationContextAwareを使ってApplicationContextを取得ことができます.これは「Contextその2」で学習しました.
3.12. Customization of the ApplicationContext」では,BeanFactoryPostProcessorを使用してApplicationContextをカスタマイズすることが説明されています.これは,「Bean その8」および「Contextその1」で学習しました.
3.13. Registering additional custom editors」では,カスタムエディタの組み込みが説明されています.これは「Bean その9」および「Contextその1」で学習しました.
ということで,今回は一気に「3.14. Setting a bean property as the result of a method invocation」へと進みます.メソッド呼び出しの結果をプロパティに設定する.うん,初めてですね.
ここまでの学習で,SpringのコンテナはあるBeanのプロパティの値(や参照)を,別のBeanのプロパティに設定してくれたりコンストラクタに引数として渡してくれることが分かりました.とってもありがたいことですが,やはり世の中それだけでは十分ではないこともあるでしょう.場合によっては,あるBeanのメソッドを呼び出したその結果をこっちのBeanのプロパティに設定したいんだ!ってこともきっとあるはず.ご安心ください,そんなあなたのためにMethodInvokingFactoryBeanをご用意させていただきました(またしても当然ですが,用意したのは私ではありません.).
MethodInvokingFactoryBeanは,「Beanその10」で学習したBeanFactoryimplementsしたもので,次のプロパティを持っています.

targetClass
staticなメソッドを呼び出す場合にそのクラス名(完全限定名)を設定するString型のプロパティです.
targetObject
staticな(インスタンス)メソッドを呼び出す場合にそのインスタンスを設定するObject型のプロパティです.
targetMethod
呼び出すメソッド名を設定するString型のプロパティです.
staticMethod
staticなメソッドを呼び出す場合に,targetClasstargetMethodの代わりに"java.lang.System.currentTimeMillis()"のように一度に設定できるString型のプロパティです.
arguments
呼び出すメソッドの引数を設定するObject型の配列のプロパティです.なお,ドキュメントではargsとなっていますが誤りです.
singleton
メソッド呼び出しの結果オブジェクトがsingletonかどうかを設定するboolean型のプロパティです.このプロパティがtrueの場合(デフォルト),メソッドの呼び出しは最初の1回だけになります.
object
メソッドを呼び出した結果の値(または参照)を保持するプロパティです.singletonfalseの場合,このプロパティが参照されるたびにメソッドが呼び出されます.

簡単そうですね.早速使ってみましょう.
おなじみの超単純なBeanを一つ用意.

package study;

public class Foo {
    private String text;
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
}

そしてBean定義XMLを作成します.

    <bean id="version" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod"><value>java.lang.System.getProperty</value></property>
        <property name="arguments">
            <list>
                <value>java.version</value>
            </list>
        </property>
    </bean>
    <bean id="foo" class="study.Foo">
        <property name="text"><ref bean="version"/></property>
    </bean>

ここでは,システムプロパティの値をfootextプロパティに設定しています.
これをいつものやつ(「Context その1」に出てくるMainクラス)で実行すると...

version : 1.4.2_03
    class=class java.lang.String
    bytes=49
foo : study.Foo@95cfbe
    text=1.4.2_03
    class=class study.Foo

となり,textプロパティにシステムプロパティの値が設定されていることが確認できました.


MethodInvokingFactoryBeanは任意のメソッドを呼び出せるので,あるクラスの初期化メソッドを呼び出すことにも使えるのではないかと一瞬思ったのですが,ちょっとうまくないようです.というのも,MethodInvokingFactoryBeanがメソッドを呼び出してくれるのは,objectプロパティが参照された場合だけなんですね.ということは,そのプロパティの値をどこかに設定しないと呼び出せないということ.じゃあ,voidなメソッドだとどうする? という疑問も.
ご安心ください,そんなあなたのために,InitializeMethodInvokingBeanをご用意させていただきました.今度はなんとこの私がご用意をさせていただきました.えっへん.
といっても全然大したことはなくて,MethodInvokingFactoryBeanを継承してみただけなんですが.
こんな感じ.

package study;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;

public class InitializeMethodInvokingBean extends MethodInvokingFactoryBean {
    public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
        super.afterPropertiesSet();
        try {
            invoke();
        }
        catch (Exception e) {
            throw new RuntimeException("method invocation failed.", e);
        }
    }
}

例外処理は思い切り手抜きしていますが気にしない,気にしない.
そして初期化メソッドを持つクラスを用意.

package study;

public class Foo {
    private String text;
    public void initialize(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
}

そしてBean定義のXMLファイルを用意.

    <bean id="initializer" class="study.InitializeMethodInvokingBean">
        <property name="targetObject"><ref bean="foo"/></property>
        <property name="targetMethod"><value>initialize</value></property>
        <property name="arguments">
            <list>
                <value>Spring is here.</value>
            </list>
        </property>
    </bean>
    <bean id="foo" class="study.Foo">
    </bean>

ここでは,initializerというBeanは,fooinitializeというメソッドを呼び出すように設定しています.
そして実行!

initializer : org.springframework.util.MethodInvoker$VoidType@1950198
    class=class org.springframework.util.MethodInvoker$VoidType
foo : study.Foo@da6bf4
    text=Spring is here.
    class=class study.Foo

ちゃんと初期化されてますね.initializerの表示がホゲホゲなのはご愛敬ということで.FactoryBeanは表示しても意味がないのです.間違いない.
でもこれ,あまり実用的とは言えなくて,かなりいかさまなんですよね.というのも,初期化メソッドの呼び出しはafterPropertiesSet()でやっているのですが,これが呼び出されるのはこの初期化メソッド呼び出しBean自身の初期化の時一回きりなんです.ですから,対象のBeanがsingletonでなければ役に立たないのです.無念だ.心より恥じる.
ということで,これまでに学習してきたプロパティの設定,コンストラクタの呼び出しに加えて,メソッド呼び出しも使えることが分かりました.工夫次第でいろいろな使い他ができそうですね.

Spring Framework 入門記 Contextその6 邪悪なSingleton

先ほどの「Context その5」を書き込んでほんの一息ついている間にid:higayasuoさんの日記からリンクが張られていました.あまりの速さにびっくりです.他にも更新から1,2分で(誤字とか修正してたのにぃ)見に来てくれる人がいたりして,皆さんどんな生活をしているのやら.(^^;
それにしてもS2の定義ファイル,すっきりしているのに柔軟そうでいいですね.それに比べると,Springの定義ファイルがいやになるくらいまどろっこしく見えてきてしまいます.このあたりはJavaBeansマンセーミニマリズム故って感じでしょうか.それでもSpring Framework入門記は続きます.なにせ日記ですから(謎).
さ,本題にはいりましょう.
 
3.15. Creating an ApplicationContext from a web application
をぃをぃ,いきなり話がWEBアプリですか.何でも,ContextLoaderListenerおよびContextLoaderServletというApplicationContextを構築するためのクラスが用意されているそうです.ContextLoaderListenerはServlet2.4のServletContextListenerを実装したクラスです.ContextLoaderServletServletContextListenerが使えない環境の場合に使用します.それぞれの場合のweb.xmlの書き方が説明されているのですが,今ここでTomcatとか動かして画面を出そうという気は全然しないので(UIきらーい),ここはこれで終わりにします.心より恥じる.
 
3.16. Glue code and the evil singleton
ちょっと強引ですが,これで3章も残すところ本節だけとなりました.そーかー,やっぱりSingletonは邪悪かー.ClassLoaderうひゃうひゃだったりすると使いづらかったりすることあるもんねー.
などと早とちりをしてしまったのですが,そういう意味ではないようです.ここで説明されているのは,BeanFactoryApplicationContextをSingletonとして使うということのようです.いけませんねー,悪の枢軸(axis of evil)のおかげで"evil"ってとっても悪いことを指すように思いこんじゃってました.どっちかというと,「シングルトンの化身」くらいの意味?
 
なぜこういうものが必要かというと,コンテナの外でインスタンス化されたオブジェクトからコンテナを使いたい場合があるから,ということです.世の中にはすでに様々なフレームワークがあって,インスタンス化はそれらにおまかせというのはIoCなコンテナに限った話ではありません.そのようなフレームワークをいろいろと組み合わせて使う場合,あっちでインスタンス化されたオブジェクトからこっちのコンテナでDependency Injectionされたオブジェクトを使いたいということもあるでしょう.そのような場合のために,Spring Frameworkが用意してくれているのがBeanFactoryLocatorです.だから"Glue code"なんですね.BeanFactoryLocatorはインタフェースなのですが,次の実装クラスが用意されています.

SingletonBeanFactoryLocator
BeanFactoryをSingletonのように取得できるBeanFactoryLocatorです.
ContextSingletonBeanFactoryLocator
ApplicationContextをSingletonのように取得できるBeanFactoryLocatorです.

だーかーらー,名前が長いんだよー.
なお,これらはSingletonと名付けられているのですが,実際のイメージはSingletonというよりService Locatorに近いかも.実はこいつら,キーを指定すると複数のコンテナを返してくれるのです.Singletonという名称は,先の実装クラスがgetInstance()でコンテナを返してくれるstaticメソッドを持っているためだと思われます.
これらのBeanFactoryLocatorXMLファイルからコンテナをインスタンス化して返してくれるのですが,そのXMLファイルのパスなどはこれまたXMLファイルで与えます.他に,JNDIからlookupして取得したパスで示されるBean定義XMLファイルでインスタンス化したコンテナを返してくれる,JndiBeanFactoryLocatorおよびContextJndiBeanFactoryLocatorも用意されています.
いずれにせよ重要なのは,BeanFactoryLocator達は何とかしてBeanFactoryを返してくれるということで,イメージ的にはBeanFacotryのファクトリです.これを覚えておきましょう.はいっ!*1
これらのBeanFactoryLocatorが返してくれるのはBeanFactoryそのものではなく,BeanFactoryReferenceです.これは,BeanFactoryの参照を返すgetFactory()というメソッドを持つインタフェースです.ややこしいですね.これを使うことで,コンテナが本当に必要とされるまでコンテナの初期化を遅延したり,コンテナへの参照を持つだけのためメモリ使用量が少ないBeanFactoryReferenceインスタンスをセッションに保存できたりして便利,ということのようです.
 
能書きはこれくらいにして,実際に動かして確認することにしましょう.でないと,わけがわからなくなりそうです.
なんといっても今回の副題は「Context その6」ですから,BeanFacotryではなくApplicationContextを使わなくてはなりません.ということで,ContextSingletonBeanFactoryLocatorを使います.
そのContextSingletonBeanFactoryLocatorですが,定義ファイルのデフォルト名を定数で持っています.その名を"beanRefContext.xml"といいます.ちなみにSingletonBeanFactoryLocatorの場合は"beanRefFactory.xml"です.そんなわけで,"beanRefContext.xml"を作成しましょう.その内容は,このBeanFactoryLocatorから取得するコンテナの定義です.

<?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="context" class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
            <list>
                <value>factory.xml</value>
                <value>beans.xml</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

なるほどー,コンテナもまた<bean>要素で定義するんですねー.ここでは,"context"という名前のApplicationContextを定義しています.このApplicationContextの定義ファイルとして,これまでと同様facotry.xmlbeans.xmlをコンストラクタの引数として与えています.
そのbeans.xmlの内容は次の通りです.いつものように<bean>要素のみ抜粋.

    <bean id="foo" class="study.Foo">
        <property name="text"><value>evil singleton</value></property>
    </bean>

なお,今回のFootextというStringのプロパティを持っています.
そして実行用のクラスなんですが,次のようになりました.

package study;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;

public class Main {
    public static void main(String[] args) {
        try {
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance();
            BeanFactoryReference ref = locator.useBeanFactory("context");
            ApplicationContext context = (ApplicationContext) ref.getFactory();

            Foo foo = (Foo) context.getBean("foo");
            System.out.println(foo.getText());

            ref.release();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最初にSingletonなやり方でBeanFactoryLocatorを取得します.ここでは引数を指定していないので,デフォルトのXMLファイルパス名である"beanRefContext.xml"が使用されます.そして,contextという名称でBeanFactoryReferenceを取得します.そこからApplicationContextを取得します.後はいつものようにBeanを取得できます.
最後に,BeanFactoryReferencerelease()メソッドを呼び出すことで,コンテナの後処理(destroySingletons())が実行されます.
これを実行すると,

evil singleton

と表示されました.やれやれ.
 
今回のBeanFactoryLocatorは,結構奥が深そうです.J2EEなどSpring Framework以外の様々なコンテナやClassLoader階層などを駆使する場合に,ここに行き着くことになるのかもしれません.今のところ十分に理解できたというわけではないのですが,非常に重要な存在っぽいということだけ忘れないことにして(それが難しい!),これで3章を終わりにしたいと思います.
さぁ,次は4章へ行くぞっ! おー!!

*1:凛ちゃん風