もっとSeasar2を味わってみる。

DIのお話を書いてみる。 - 都元ダイスケ IT-PRESSの続きです。

まずは、Tableクラスをちょっと変えちゃいましょう*1

import java.util.List;

public class Table {
    public String name;
    public List<Column> columns;

    public Table(String name) {
            this.name = name;
    }
}

columnsフィールドの初期化がなくなっただけ。ここでピンと来たあなたは凄い。

まずはMain

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class Main {

    public static void main(String[] args) {
        SingletonS2ContainerFactory.init();
        S2Container container = SingletonS2ContainerFactory.getContainer();
        
        Table table = (Table) container.getComponent(Table.class);
        BusinessLogic business = (BusinessLogic) container.getComponent("postgresqlBusiness");
        business.doBusiness(table);
        
        container.destroy();
    }
}

うお、Tableのnewが消えた。そう、DIコンテナの「依存性管理機能」は「ifを駆逐」し、「インスタンス管理機能」は「newを駆逐」する。

よく考えると、TableってColumnに依存してるよね。じゃあコンテナ任せでしょう、というDI脳を発揮。

ここのポイントは「getComponentの引数にClass*2を与えている事」。

getComponent(String)は「この名前のコンポーネントくれよ」で、getComponent(Class)は「このクラスのインスタンスくれよ」になる。

では、いよいよapp.diconを見てみよう。

さすがに、記述量は増える。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<component class="Table">
		<arg>"T_HOGE"</arg>
		<property name="columns">hogeColumns</property>
	</component>
	<component name="hogeColumns" class="java.util.ArrayList">
		<initMethod name="add">
			<arg>
				<component class="Column">
					<arg>"ID"</arg>
					<arg>"integer"</arg>
				</component>
			</arg>
		</initMethod>
		<initMethod name="add">
			<arg>
				<component class="Column">
					<arg>"CONTENTS"</arg>
					<arg>"string"</arg>
				</component>
			</arg>
		</initMethod>
	</component>

	<component name="mysqlBusiness" class="BusinessLogic">
		<arg>mysql</arg>
	</component>
	<component name="postgresqlBusiness" class="BusinessLogic">
		<arg>postgresql</arg>
	</component>
	<component class="MySQLConverter" />
	<component class="PostgreSQLConverter" />
</components>

上から見ていきましょう。

まずTableコンポーネントのポイント解説

まず、TableコンポーネントはgetComponent(Class)で呼びますので、名前を付ける必要がない。

そしてはコンストラクタに与える引数。前回のエントリで説明しましたね〜。ちなみに「コンストラクタ経由で依存性を注入すること」を「コンストラクタ・インジェクション」と呼びます。ここではコンストラクタに「T_HOGE」という文字列を渡せ、という指示をしています。""で囲めば文字列扱い*3

そしては、newした後で、その名前のプロパティに代入する事によって依存性を注入しろ、という指示。これを「プロパティ・インジェクション」*4と呼びます。ここでは、columnsフィールドにhogeColumnというコンポーネントを代入しろ、という指示をしています。

次にhogeColumnコンポーネントのポイント解説

実体はjava.lang.ArrayList。別に自作のクラスじゃなくたってコンポーネント化できるんです。

ここで出てくるのは。これは、newした後で、その名前のメソッドを呼べ、という指示。これを「メソッド・インジェクション」*5と呼びます。ここでは、addメソッドを呼んで、引数にColumnコンポーネントを渡せ、という指示をしています。

の中は、別にコンポーネント名や文字列に限定されない。その内部に直接コンポーネント定義を書いてしまっても良い、という例っすね。

内部のColumnコンポーネントについては、今まで読んできた知識で読めると思う。

あとは、ビジネスロジック系のコンポーネント。以前と大差ありません。

以上により、

Tableコンポーネントを呼び出すと、従来のようにColumnという依存性が注入された状態で、Tableクラスのインスタンスがもらえるようになります。めでたしめでたし。
はい、罠に気づきましたか?

mysqlBusinessコンポーネントのコンストラクタに指定されているmysqlっていうコンポーネント*6って、どこにも名前定義されてなくね??

下で定義されている2つのコンバータ定義には、名前ついてません。ここもDIコンテナが、よしなに「mysqlっていうコンポーネント名だから、MySQLConverterクラスだよね」とか空気読むんでしょうかね。

さすがにそれは無いw そこまでやられるとキモくないですかw

というわけで、正解のコード。

import org.seasar.framework.container.annotation.tiger.Component;

@Component(name="mysql")
public class MySQLConverter implements Converter {
    // 同じなので略
}
import org.seasar.framework.container.annotation.tiger.Component;

@Component(name="postgresql")
public class PostgreSQLConverter implements Converter {
    // 同じなので略
}

というわけで、クラス自体にComponentアノテーションをつけると、コンポーネントをソース側で命名することができる、というオチでした。

以上、Seasar2をもっと味わってみる、でした。

*1:変えなくてもいいんだけど、不要になるので。

*2:通称、クラスクラス。クラスを表すオブジェクト。クラス名に".class"を付けると、そのクラスを表すオブジェクトが取得できる。ちなみに、変数(インスタンス)に対してgetClassメソッドを呼ぶと、同様のモノが手に入る。この辺りは、詳しく知りたければリフレクションについてググるべし。

*3:細かく言えばOGNLって奴なんですが、ここではスルー。

*4:今回は、publicフィールドを利用し、それに代入するダケなのでプロパティインジェクションという名前になります。もしprivateフィールドで、setColumnsが用意されていれば、同じであっても、「セッター・インジェクション」と呼びます。

*5:メソッドを呼ぶだけで、もしかしたら依存性は注入されないかもしれない(全てのメソッドが、依存性注入の為に存在する訳ではない為)。けど、一般的に、依存性を注入する為に呼びます。

*6:postgresqlも同様。