BeanFactory の階層構造

謎めきすぎて自分が書いていいものか分かりませんが,ちょっと書いてみるテスト.でも,Spring 入門記を終えて長いので信憑性低いですから!! 残念!!!!


Spring のコンテナである BeanFactory の階層といった場合,次の三つの階層があると思います.

おそらく多くの人が興味あるのは最後のやつだと思うのですが,一応順番に一つずつ.


interface の階層構造
S2 と違って,Spring の場合たくさんの interface が用意されています.Javainterface は多重継承が出来るので,単なる階層構造ではないのですが,一応主なものを書き出してみると...

  • BeanFactory
    • ListableBeanFactory
    • HierarchicalBeanFactory
      • ConfigurableBeanFactory
        • ConfigurableListableBeanFactory

ConfigurableListableBeanFactoryListableBeanFactoryextends しています.
それぞれの役割はほぼ見たまんま.
ここで注意したいのは HierarchicalBeanFactory.階層化することの出来る BeanFacotryinterface です.でも,getParentBeanFactory() が定義されているだけなんですけどね.(^^;


Spring にはもうひとつ,ApplicationContext というコンテナもあって,こちらは次のようになっています.

  • ApplicationContext
    • ConfigurableApplicationContext

ApplicationContext は,ListableBeanFactoryHierarchicalBeanFactoryextends していますが,ConfigurableApplicationContextConfigurableBeanFactoryextends していません.なんか微妙.
という具合にいろいろ細分化されているのですが,実際にはほとんど意味がありません.なぜなら,実装クラスはそこまで細かく用意されているわけではないからです.


class の階層構造
実装クラスもいろいろ用意されています.
ますは BeanFactory の実装クラス.

  • AbstractBeanFactory
    • AbstractAutowireCapableBeanFactory
      • DefaultListableBeanFactory
        • StaticListableBeanFactory
        • XmlBeanFactory

AbstractBeanFactory は,ConfigurableBeanFactoryimplements しています.つまり,BeanFactory だけ,あるいは HierarchicalBeanFactory だけを implements した実装は用意されていません.


ApplicationContext の実装クラスは次のようになっています.

  • AbstractApplicationContext
    • AbstractRefreshableApplicationContext
      • AbstractXmlApplicationContext
        • ClassPathXmlApplicationContext
        • FileSystemXmlApplicationContext
    • GenericApplicationContext
      • StaticApplicationContext

AbstractApplicationContext は,ConfigurableApplicationContextimplements しています.つまり,ApplicationContext だけを implements した実装は用意されていません.
興味深いのは,XML による定義ファイルを扱うためにコンテナのサブクラスが用意されていることです.しかも ApplicationContext ではクラスパスから XML を見つけるのか,ファイルシステムから見つけるのかが違うだけのものまで.一方 S2 では,コンテナの実装クラスは一つしか用意されていません.それでいながら,XML による定義ファイルだけでなく,Groovy による定義ファイルを使うことも出来ます.これは,コンテナではなくコンテナビルダーを複数使い分けることで実現されています.コンテナそのものと,コンテナをセットアップすることを分けているわけです.Spring は interfaceclass を細かく分割している割に,責務の分け方はイマイチだと思います.


インスタンスの階層構造
さて,おそらく本命であろうインスタンスの階層構造ですが,実はよく分かっていません (苦笑).入門記でも結局扱ってないような.だってリファレンスにほとんど情報がありませんから!! 残念!!!!
そんなわけで (どんなわけで?),以下に書いてあることは信憑性 0 くらいということで.(^^;


まず,Spring におけるコンテナの階層構造は,前述の HierarchicalBeanFactory によって定義されます.でも,これはさっきも書いたように親のコンテナを取得することが出来る,というだけです.実際には HierarchicalBeanFactory でない BeanFactory の実装は見あたらないので,AbstractBeanFactory を調べればよいことになります.すると...
興味深いことが分かります.AbstractBeanFactory は,親コンテナへの参照は保持していますが,親は子のコンテナを保持していないのです!! そして,BeanFactory#getBean(String)JavaDoc にも,興味深い記述があります.

This method delegates to the parent factory if the bean cannot be found in this factory instance.

もし,メソッドが呼び出されたコンテナに該当の Bean が存在しなければ,親のコンテナを探しに行くとだけ書かれています.つまり,子のコンテナからは探してくれないのですね.
確認してみましょう.
まずは単純な Bean を用意.

package hoge;

public class Model {
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

そして定義ファイル.今回は 3 つも用意.
まずは定義ファイルをひとまとめに扱う SingletonBeanFactoryLocator が読み込むための beanRefFactory.xml

<?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="foo" lazy-init="true"
        class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
      <value>foo.xml</value>
    </constructor-arg>
  </bean>
 
  <bean id="bar" lazy-init="true"
        class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
      <list>
        <value>bar.xml</value>
      </list>
    </constructor-arg>
    <constructor-arg>
      <ref bean="foo"/>
    </constructor-arg>
  </bean>
</beans>

ここでは,2 つのコンテナを定義しています.親 (foo) と子 (bar) です.
そんなわけで (どんなわけで?),親の定義ファイル,foo.xml

<?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="yuri" class="hoge.Model">
        <property name="name"><value>Yuri Ebihara</value></property>
    </bean>
</beans>

そして子の定義ファイル bar.xml

<?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="moe" class="hoge.Model">
        <property name="name"><value>Moe Oshikiri</value></property>
    </bean>
</beans>

そして実行用のクラス.

package hoge;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.SingletonBeanFactoryLocator;

public class Main {
    public static void main(String[] args) {
        try {
            BeanFactoryLocator locator = SingletonBeanFactoryLocator.getInstance();

            BeanFactory foo = locator.useBeanFactory("foo").getFactory();
            BeanFactory bar = locator.useBeanFactory("bar").getFactory();

            Model yuri = (Model) foo.getBean("yuri");
            System.out.println(yuri.getName());

            Model moe = (Model) bar.getBean("moe");
            System.out.println(moe.getName());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

まずは親と子それぞれのコンテナから,自身が持つ Bean を取り出しています.
こいつを実行!!

Yuri Ebihara
Moe Oshikiri

ふむ.
次に,二つの Bean (yuri と moe) をともに bar から取得するようにして実行!!

Yuri Ebihara
Moe Oshikiri

bar には yuri はいないはずですが,親である foo から探してきてくれました.
次に,二つの Bean を foo から取得するようにして実行!!

Yuri Ebihara
org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No bean named 'moe' is defined: 
        org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [yuri]; 
            root of BeanFactory hierarchy

ぐはぁっ (お約束).
もえちゃんを見つけてくれませんでした.やはり,子のコンテナから Bean を探したりはしないようです.


これで,Spring のコンテナは自分にない Bean は親へ探しに行くだけで,子から探すことはないということが確認できました.これは見事なまでに S2 と対照的ですね.S2 では,コンテナは自分にないコンポーネントは子のコンテナから探してくれますが,親からは探してくれません.ですよね?
なんという方針の違い.(^^;
おそらく,Spring ではデフォルトのコンポーネントを上の方のコンテナで定義して,下の方のコンテナでは必要に応じて別のコンポーネントを登録 (オーバーライド) する,という使い方を想定しているのでしょうね.一方で S2 は標準的なコンポーネントは必要に応じてインクルードすることで,子のコンテナに持たせることになります.


もうひとつ,Spring では,定義ファイルの分割はコンテナの階層構造とは別ものであることに注意するべきかもしれません.
実際,先の例での foo.xmlbar.xml を一つのコンテナに読み込ませることが出来ます.

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

一方,S2 では基本的に一つの定義ファイルに一つのコンテナが対応することになると思います.分割された定義ファイルを扱うにはインクルードを使うことになり,すると子のコンテナが作られます.
このような違いがあるため,Spring では S2 に比べて子のコンテナからコンポーネントを検索する必要があまりないのかもしれません.
そのせいか,Spring ではコンテナの階層構造を一つのファイルで集中管理する形になっています.もしかしたら beanRefFactory.xml の中でさらに BeanFactoryLocator を使うのもありなのかもしれませんが,基本的にBeanFactoryLocator はシングルトンみたいなものなので,そんなことはしないはず.一方 S2 ではコンテナの階層構造は個々の dicon ファイルに記述されることになります.これまた大きな違い.
そこそこの規模ならコンテナ (というより定義ファイル) 間の依存関係を一つのファイルで確認できるのは悪くない気もしますが,大きくなってきたら破綻しそう.S2 の場合はそれぞれの定義ファイルが必要なものをインクルードしていて自己記述的かつ完結しているので,大規模になっても困ることは少なそうです.


コンテナの階層構造とその管理,そしてコンポーネントが見える範囲というのは,定義ファイルの表現力以上に Spring と S2 の大きな違いかもしれませんね.