コンポーネントの生成
さて無事に生成された S2Container ですが、次はどのように S2Container に保持された ComponentDef からオブジェクトが生成されるのかを見てみたいと思います。
まず、S2Container に登録された ComponentDef は、抽象化されたインターフェース S2ContainerBehavior#Provider から取得されます(大抵の場合に於いては)。S2ContainerImpl#getComponent() で、S2Container は S2ContainerBehavior#Provider から取得した ComponentDef を手がかりに、 ComponentDef#getComponent() を呼び出してインスタンスを取得します。ComponentDefImpl#getComponent() は以下のように実装されています。
public Object getComponent() { return getComponentDeployer().deploy(); }
インターフェース ComponentDeployer が利用されています。ここで ComponentDef に組み込まれた種々の情報を組み立ててインスタンスを生成するようです。
/** * インスタンス定義に応じてインスタンス生成や外部コンテキストへの配備などを行った後に、 そのコンポーネントのインスタンスを返します。 * * @return コンポーネントのインスタンス * * @see org.seasar.framework.container.deployer.SingletonComponentDeployer#deploy() * @see org.seasar.framework.container.deployer.PrototypeComponentDeployer#deploy() * @see org.seasar.framework.container.deployer.ApplicationComponentDeployer#deploy() * @see org.seasar.framework.container.deployer.RequestComponentDeployer#deploy() * @see org.seasar.framework.container.deployer.SessionComponentDeployer#deploy() */ public Object deploy();
段々コピペが多くなってきましたが、気にせず続けます。SingletonComponentDeployer では以下のようにインスタンスを組み立てています。循環参照などもここで検知されるようです。
private void assemble() { if (instantiating) { throw new CyclicReferenceRuntimeException(getComponentDef() .getComponentClass()); } instantiating = true; try { component = getConstructorAssembler().assemble(); } finally { instantiating = false; } getPropertyAssembler().assemble(component); getInitMethodAssembler().assemble(component); }
ConstructorAssembler が ComponentDef を元に組み立てています。AbstractConstructorAssembler を見てみましょう。引数が存在する場合には ArgDef から引数を取得しているのが分かります。
/** * コンポーネント定義に基づいてコンポーネントを組み立てます。 * * @return コンポーネント */ protected Object assembleManual() { Object[] args = new Object[getComponentDef().getArgDefSize()]; for (int i = 0; i < args.length; ++i) { try { args[i] = getComponentDef().getArgDef(i).getValue(); } catch (ComponentNotFoundRuntimeException cause) { throw new IllegalConstructorRuntimeException(getComponentDef() .getComponentClass(), cause); } } BeanDesc beanDesc = BeanDescFactory.getBeanDesc(getComponentDef() .getConcreteClass()); return beanDesc.newInstance(args); } /** * デフォルトのコンストラクタを使ってコンポーネントを組み立てます。 * * @return コンポーネント */ protected Object assembleDefault() { Class clazz = getComponentDef().getConcreteClass(); Constructor constructor = ClassUtil.getConstructor(clazz, null); return ConstructorUtil.newInstance(constructor, null); }
また、PropertyAssembler や InitMethodAssembler によって、生成されたコンポーネントに対するプロパティ・インジェクションおよびメソッド・インジェクションが実行されています。なんだかインジェクションという言葉が多くて混乱してしまいますが、property や initMethod は dicon ファイルの Component タグで設定可能なコンポーネント生成時の振る舞いを設定するものです。普段あまり使う機会は無さそうです。詳しくは Seasar のリファレンスガイドを参照しましょう。
これで Seasar がどのようにコンポーネントを生成しているのか分かりました。ちなみにここで ArgDef を使っていますが、ここでの ArgDef は dicon ファイルの arg タグで指定した引数です。・・・あれあれ、Seasar でインジェクションされたコンポーネントには、引数に指定されたインターフェースの実装を勝手に組み込んでくれるんだけど、それはどのように実現しているんだろう?
S2Container が勝手にコンポーネントを注入してくれる機能は「自動バインディング」と呼ぶそうです。早速調べてみると、あったあった AutoBindingDef というインターフェースが存在しますね。ここに2つのメソッドが定義されています。
/** * 自動バインディング定義に基づき、 <code>componentDef</code>に対する{@link ConstructorAssembler}を返します。 * * @param componentDef * コンポーネント定義 * @return 自動バインディングの範囲が設定された{@link ConstructorAssembler} */ ConstructorAssembler createConstructorAssembler(ComponentDef componentDef); /** * 自動バインディング定義に基づき、 <code>componentDef</code>に対する{@link PropertyAssembler}を返します。 * * @param componentDef * コンポーネント定義 * @return 自動バインディングの範囲が設定された{@link PropertyAssembler} */ PropertyAssembler createPropertyAssembler(ComponentDef componentDef);
先ほど SingletonComponentDeployer では、getConstructAssembler() により抽象クラス AbstractComponentDeployer からアセンブラを取得していました。よく見るとここで AutoBindingDef からアセンブラをセットアップしています。自動バインディングを行う場合、AutoConstructorAssembler が使用されるようです。では、AutoConstructorAssembler で実際にどのようにインスタンスが生成されるか見てみます。
protected Object doAssemble() { Constructor constructor = getSuitableConstructor(); if (constructor == null) { return assembleDefault(); } Object[] args = getArgs(constructor.getParameterTypes()); return ConstructorUtil.newInstance(constructor, args); } /** * 引数を返します。 * * @param argTypes * @return 引数 */ protected Object[] getArgs(Class[] argTypes) { Object[] args = new Object[argTypes.length]; for (int i = 0; i < argTypes.length; ++i) { try { args[i] = getComponentDef().getContainer().getComponent( argTypes[i]); } catch (ComponentNotFoundRuntimeException ex) { // logging ... args[i] = null; } } return args; }
おぉ、出てきました。ComponentDef から S2Container を取得し、更に getComponent(argTypes[i]) していますね。こうして同じコンテナに登録されているコンポーネントを引数に登録するようです。
一通り謎も解けたので、次回は再度 S2Container の生成部分を見直してみます。