Seasar DI Container with AOP

S2Containerの概要

S2Containerでは、DIContainer機能を提供しています。DIContainerとは、Dependency Injectionをおこなう軽量コンテナです。Dependency Injectionとは、インターフェースと実装を分離してコンポーネント同士がインターフェースのみで会話するようにし、実際のコンポーネントの生成や設定を、外出しにしようという考え方です。Dependency InjectionについてはMartin Fowler の「Inversion of Control Containers and the Dependency Injection pattern」で分かりやすく説明されています。コンポーネントとは、ここではJavaのクラスのことをいいます。複数のコンポーネントを格納・管理しているのでコンテナと呼び、軽量は、EJBのコンテナなどと比べ手軽に使えるという意味です。

DIContainerのイメージは、次のようになります。

図のようにDIContainerに対して要求すると、DIContainerはコンテナ内を検索してインターフェースに関連したコンポーネント(実装クラス)のインスタンスを返します。インターフェースとコンポーネントの関連づけは、自動でDIContainerが行います。コンポーネントのコンテナ登録は、設定ファイルを定義するだけで簡単にできます。DIContainerが、コンポーネントの管理・設定を行ってくれるためプログラムはインターフェースのみを意識することになります。実際に図中のインターフェース(Hoge)とコンポーネント(HogeImpl)の要求・取得をプログラムで表すと、次のようになります。

public interface Hoge {

    public void showMessage();
}
public class HogeImpl implements Hoge {

    public void showMessage() {
        System.out.println("Hello World!");
    }
}

インターフェースでは showMessage()を定義し、実装クラスでは、showMessage()で"Hello World!"を表示しています。

<components>
    <component class="examples.dicon.HogeImpl"/>
</components>

コンポーネントをコンテナに登録する定義は、これだけです。詳しい定義方法は、S2Containerの定義を参照してください。次は、実行クラス(インターフェースと会話をする部分)です。

public class HelloClient {

    private static String PATH = "examples/dicon/Hello.dicon";
	
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hoge hoge = (Hoge) container.getComponent(Hoge.class);
        hoge.showMessage();
    }
}

実装クラス(HogeImpl)が登場していないことから、使う側はインターフェースのみを意識すればよいということがわかると思います。S2Containerの詳しい使用方法は、S2Containerリファレンスを参照してください。

今までと何が違うのか

コンポーネント同士がインターフェースのみで会話することにより依存関係をなくすという考え方は、従来からオブジェクト指向の世界にありました。しかし、開発時にインターフェースのみで依存関係をなくすように設計しても、完成までにはソースコードのどこかに依存関係を記述する必要がありました。その依存関係をDIContainerを用いて、実行時に外から組み立てるという考え方が今までと違う点です。

何がうれしいのか

DIContainerのメリットには次の点が挙げられます。

  • メンテナンス性の向上
    - 実装クラス同士の依存度が下がるため後からロジックを変更してもその影響範囲を極小化できます。
  • 品質の向上
    - モックを使って簡単にテストできるようになるためテスタビリティが向上します。
  • 開発期間の短縮
    - 使いたい機能の実装が出来上がってなくても実装・テストをおこなえるため並行して作業できるようになります。
  • 再利用性の向上
    - コンポーネントは通常のJavaのクラス(POJO)であり特定のAPI(EJBなど)に依存しないので再利用できる機会が増えます。

S2Containerリファレンス

作成すべきファイル

S2Containerを使用するためには、定義ファイルを作成する必要があります。
定義ファイルは、コンポーネントを組み立てるための設計書のようなものです。
形式はXMLで、拡張子は、diconです。diconは、ダイコンと読みます。

S2Containerの定義

S2Containerの定義は、次のようになります。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="..." class="...">
            ...
    </component>
    <component name="..." class="...">
            ...
    </component>
</components>

DOCTYPEは省略できません。diconファイルを作成する場合は、上記のサンプルをコピー&ペーストしてください。ルートはcomponentsタグです。コンポーネントごとに、componentタグを定義していきます。componentタグのclass属性でコンポーネントのクラスの完全限定名を指定します。name属性には、コンポーネント名を指定します。詳細は、S2Container定義タグリファレンスを参照してください。

<components>
    <component name="hoge" class="examples.dicon.HogeImpl"/>
</components>

S2Containerの生成

S2Containerを作成する場合は、次のメソッドを使用します。

- org.seasar.framework.container.factory.S2ContainerFactory#create(String path)

引数pathはCLASSPATHで指定されているディレクトリをルートとする設定ファイルの絶対パスです。例えば、WEB-INF/classes/aaa.dicon の場合は aaa.dicon に、WEB-INF/classes/aaa/bbb/ccc.dicon の場合は aaa/bbb/ccc.dicon になります。セパレータは、WindowsでもUnixでも/です。
private static final String PATH = "aaa/bbb/ccc.dicon";
...
S2Container container = S2ContainerFactory.create(PATH);

コンポーネントの取得

S2Containerからコンポーネントを取り出すには、次のメソッドを使用します。

- org.seasar.framework.container.S2Container#getComponent(Object componentKey)

引数には、コンポーネントのクラスもしくはコンポーネント名を指定できます。詳しくは、componentタグを参照してください。コンポーネントのクラスを指定する場合、コンポーネント instanceof クラスがtrueを返すクラスなら指定することができます。しかし、S2Containerの中に指定したクラスを実装しているコンポーネントが複数ある場合、S2Containerは、どのコンポーネントを返せばよいのか判断できないため、TooManyRegistrationRuntimeExceptionが発生します。実装コンポーネントがユニークに決まるクラスを指定してください。コンポーネント名で取得することもできます。その場合も、同一の名前をもつコンポーネントが複数登録されている場合、TooManyRegistrationRuntimeExceptionが発生します。コンポーネント名指定の場合、スペルミスをする可能性もあるので、できるだけクラス指定のほうが良いでしょう。

例) クラスを指定してコンポーネントを取得する場合

S2Container container = S2ContainerFactory.create(PATH);
Hoge hoge = (Hoge) container.getComponent(Hoge.class);

例) コンポーネント名を指定してコンポーネントを取得する場合

S2Container container = S2ContainerFactory.create(PATH);
Hoge hoge = (Hoge) container.getComponent("hoge");

Dependency Injectionのタイプ

Dependency Injectionには、コンポーネントの構成に必要な値をコンストラクタで設定する(Constructor Injection)のか、セッター・メソッドで設定する(Setter Injection)のか、初期化メソッドで設定する(Method Injection)のかで、タイプが分かれます。Method InjectionはS2のオリジナルです。S2はすべてのタイプとそのハイブリッド型もサポートします。

コンストラクタ・インジェクション

コンストラクタ・インジェクションとは、任意のコンストラクタの引数値にDependency Injectionします。
S2Containerの定義ファイルには、次の内容を記述します。

  • コンポーネントの指定
    コンポーネントは、componentタグで組み立てます。class属性でクラス名を指定します。
    name属性でコンポーネントに名前を付けることもできます。
  • コンストラクタの引数の指定
    コンポーネントのコンストラクタの引数は、componentタグの子タグであるargタグを使って指定します。
    文字列の場合は、ダブルコーテーション(")で囲みます。
<components>
    <component name="..." class="...">
          <arg>...</arg>
    </component>
</components>
詳しい使用方法は、Exampleのコンストラクタ・インジェクションを参照してください。

セッター・インジェクション

セッター・インジェクションとは、任意のプロパティにセッターメソッドを使用してDependency Injectionします。
S2Containerの定義ファイルには、次の内容を記述します。

  • コンポーネントの指定
    コンポーネントの指定は、コンストラクタ・インジェクションと同様です。
    name属性でコンポーネントに名前を付けることもできます。
  • プロパティの指定
    コンポーネントのプロパティは、componentタグの子タグであるpropertyタグを使って指定します。
    name属性でプロパティ名を指定します。
<components>
    <component name="..." class="...">
          <property name="...">...</property>
    </component>
</components>
詳しい使用方法は、Exampleのセッター・インジェクションを参照してください。

メソッド・インジェクション

メソッド・インジェクションとは、任意のメソッドを呼び出して、Dependency Injectionします。
S2Containerの定義ファイルには、次の内容を記述します。

  • コンポーネントの指定
    コンポーネントの指定は、コンストラクタ・インジェクションと同様です。
    name属性でコンポーネントに名前を付けることもできます。
  • 初期化メソッドの指定
    initMethodタグを使って、コンポーネントの任意のメソッドを呼び出します。name属性で、メソッド名を指定します。引数は、argタグを子タグに使います。name属性を省略して、ボディで、OGNL式を使うこともできます。その際、コンポーネント自身は#selfで表します。
<components>
    <component name="..." class="...">
          <initMethod>...</initMethod>
    </component>
</components>
詳しい使用方法は、Exampleのメソッド・インジェクションを参照してください。

S2Container定義の分割とインクルード

すべてのコンポーネントを1つのファイルに記述すると、直ぐに肥大化してしまい管理が難しくなります。そのため、コンポーネントの定義を複数に分割する機能と分割された定義をインクルードして1つにまとめる機能がS2Containerにあります。S2Container定義ファイルのインクルードは次のようにして行います。

<components>
    <include path="bar.dicon"/>
</components>

includeタグのpath属性で取り込みたいS2Container定義ファイルのパスを指定します。詳しくは、includeタグを参照してください。
コンポーネントの検索順は、先ず自分自身に登録されているコンポーネントを探して、見つからない場合は、includeされている順に子供のS2Containerに登録されているコンポーネントを検索し、最初に見つかったコンポーネントが返されます。
次のような場合は、Foo(自身のコンポーネント)→aaa(子供のS2Container)→bbb(子供のS2Container)の順に検索します。

<components>
    <include path="aaa.dicon"/>
    <include path="bbb.dicon"/>
    <component class="example.container.Foo" />
</components>

自動でコンストラクタ・インジェクションセッター・インジェクションを行う場合、S2Containerはインクルード先のコンポーネントを自動インジェクションすることができます。自動でDependency Injectionを行う場合の条件は自動バインディングを参照してください。
次のようにセッター・インジェクションでプロパティに指定するコンポーネントがインクルード先のaaa.diconとbbb.diconに登録されている場合、各HelloClientでは、どちらのコンポーネントが使用されるかをみてましょう。

root.dicon
<components>
    <include path="examples/dicon/include/aaa.dicon"/>
    <include path="examples/dicon/include/bbb.dicon"/>
    <component name="root" class="examples.dicon.include.RootHelloClient"/>
</components>
aaa.dicon
<components>
    <component class="examples.dicon.include.HelloImpl">
        <property name="Message">"Hello Aaa!"</property>
    </component>

    <component name="aaa" class="examples.dicon.include.AaaHelloClient"/>
</components>
bbb.dicon
<components>
    <component class="examples.dicon.include.HelloImpl">
        <property name="Message">"Hello Bbb!"</property>
    </component>

    <component name="bbb" class="examples.dicon.include.BbbHelloClient"/>
</components>

各コンポーネントの内容は、次のようになります。

package examples.dicon.include;

public interface HelloClient {

    public void showMessage();
}
package examples.dicon.include;

public class RootHelloClient implements HelloClient {

    private Hello hello_;

    public void setHello(Hello hello) {
        hello_ = hello;
    }

    public Hello getHello() {
        return hello_;
    }

    public void showMessage() {
        System.out.println(getHello().getMessage());
    }
}

AaaHelloClientとBbbHelloClientはRootHelloClient同様の実装です。

package examples.dicon.include;

public interface Hello {

    public void setMessage(String helloMessage);

    public String getMessage();
}
package examples.dicon.include;

public class HelloImpl implements Hello {

    private String helloMessage_;

    public void setMessage(String helloMessage) {
        helloMessage_ = helloMessage;
    }

    public String getMessage() {
        return helloMessage_;
    }
}

HelloImplはMessageプロパティを定義しているだけです。各HelloClientのshowMessage()を呼び出した場合の実行結果は次のようになります。

RootHelloClientの実行結果
Hello Aaa!

まず、S2Containerはroot.diconにHelloImplが登録されているかを検索します。root.diconにはないので、次にインクルード先のaaa.diconを検索します。aaa.diconにはHelloImplが登録されているので、そのコンポーネントを使用します。

AaaHelloClientの実行結果
Hello Aaa!

AaaHelloClientは、aaa.diconに登録されているコンポーネントを使用します。自動でインジェクションを行う場合、子供のS2Containerは親のS2Containerのコンポーネントを使用することはできません。例えば、root.diconにHelloImplを登録していてもAaaHelloClientには自動インジェクションされないということです。

BbbHelloClientの実行結果
Hello Bbb!

AaaHelloClientと同様にBbbHelloClientもbbb.diconに登録されているコンポーネントを使用します。
このサンプルは、seasar2/src/examples/dicon/include以下に用意されています。

名前空間

コンポーネントの定義を分割した場合に、複数のコンポーネント定義間で名前が衝突しないように、componentsタグのnamespace属性で名前空間を指定することができます。

foo.dicon
<components namespace="foo">
    <component name="aaa" .../>
    <component name="bbb" ...>
        <arg>aaa</arg>
    </component>
</components>
bar.dicon
<components namespace="bar">
    <include path="foo.dicon"/>
    <component name="aaa" .../>
    <component name="bbb" ...>
        <arg>aaa</arg>
    </component>
    <component name="ccc" ...>
        <arg>foo.aaa</arg>
    </component>
</components>
app.dicon
<components>
    <include path="bar.dicon"/>
</components>

同一のコンポーネント定義内では、名前空間なしで参照できます。他のS2Container定義のコンポーネントを参照する場合は、名前空間.をコンポーネント名の頭につけます。foo.aaaとbar.aaaは同じ名前がついていますが、名前空間が異なっているので、違うコンポーネントとして認識されます。慣習として、定義ファイルの名前は、名前空間.diconにすることを推奨します。

インスタンス管理

S2Containerで、コンポーネントのインスタンスをどのように管理するのかを指定するのが、componentタグのinstance属性です。デフォルトはsingletonで、これは、S2Container.getComponent()によって返されるコンポーネントは常に同じだという意味です。S2Container.getComponent()を呼び出すたびに、新たに作成されたコンポーネントを返して欲しい場合は、instance属性にprototypeを指定します。リクエスト(HttpServletRequest)ごとにコンポーネントを管理したい場合は、instance属性にrequestを指定します。セッション(HttpSession)ごとにコンポーネントを管理したい場合は、instance属性にsessionを指定します。

プレゼンテーションのフレームワークと組み合わせるときに、プレゼンテーションフレームワークが作成したインスタンスに対して、S2Containerで管理されているコンポーネントをセットしたい場合があります。そのようなS2Container外のコンポーネントに対してDependency Injectionしたいときには、次のメソッドを使用します。

- org.seasar.framework.container.S2Container#injectDependency(Object outerComponent)
- org.seasar.framework.container.S2Container#injectDependency(Object outerComponent, Class componentClass)
- org.seasar.framework.container.S2Container#injectDependency(Object outerComponent, String componentName)

第一引数には、外部のコンポーネントを指定します。第二引数には、外部コンポーネントのクラス、またはコンポーネント名を指定します。
そのとき、S2Container定義では、instance属性にouterを指定します。
instance属性 説明
singleton(default) S2Container.getComponent()を何度呼び出しても同じインスタンスが返されます。
prototype S2Container.getComponent()を呼び出すたびに新たなインスタンスが返されます。
request リクエスト毎に1つのインスタンスが作成されます。name属性に指定した名前で、コンポーネントがリクエストに格納されます。
session セッション毎に1つのインスタンスが作成されます。name属性に指定した名前で、コンポーネントがセッションに格納されます。
outer コンポーネントのインスタンスは、S2Container外で作成し、Dependency Injectionだけを行います。アスペクトコンストラクタ・インジェクションは適用できません。

ライフサイクル

initMethodやdestroyMethodでコンポーネントのライフサイクルもコンテナで管理することができます。S2Containerの開始時(S2Container.init())にinitMethodタグで指定したメソッドが呼び出され、S2Containerの終了時(S2Container.destroy())にdestroyMethodタグで指定したメソッドが呼び出されるようになります。initMethodはコンポーネントがコンテナに登録した順番に実行され、destroyMethodはその逆順に呼び出されることになります。instance属性がsingleton以外の場合、destroyMethodを指定しても無視されます。java.util.HashMap#put()メソッドに初期化(aaaに111を設定)・終了処理(aaaにnullを設定)を設定する場合は、次のようになります。

<components namespace="bar">
    <component name="map" class="java.util.HashMap">
        <initMethod name="put">
            <arg>"aaa"</arg>
            <arg>111</arg>
        </initMethod>
        <destroyMethod name="put">
            <arg>"aaa"</arg>
            <arg>null</arg>
        </destroyMethod>
    </component>
</components>

自動バインディング

コンポーネント間の依存関係は、型がインターフェースの場合、コンテナによって自動的に解決されます。これがS2Containerのデフォルトですが、componentタグのautoBinding属性を指定することで細かく制御することもできます。

autoBinding 説明
auto(default)

コンストラクタの引数が明示的に指定されている場合は、それに従います。
指定されていない場合、引数のないデフォルトコンストラクタが定義されている場合はそのコンストラクタを使います。
デフォルトのコンストラクタがない場合、コンストラクタの引数の数が1以上で、引数の型がすべてインターフェースのコンストラクタで最も引数の数が多いものを使います。
プロパティが明示的に指定されている場合はそれに従います。
明示的に指定されていないプロパティで、型がインターフェースの場合は自動的にバインドします。

constructor コンストラクタの引数が明示的に指定されている場合は、それに従います。
指定されていない場合、引数のないデフォルトコンストラクタが定義されている場合はそのコンストラクタを使います。
デフォルトのコンストラクタがない場合、コンストラクタの引数の数が1以上で、引数の型がすべてインターフェースのコンストラクタで最も引数の数が多いものを使います。
プロパティが明示的に指定されている場合は、それに従います。
property コンストラクタの引数が明示的に指定されている場合は、それに従います。
指定されていない場合は、デフォルトのコンストラクタを使います。
型がインターフェースのプロパティを自動的にバインドします。
none コンストラクタの引数が明示的に指定されている場合は、それに従います。
プロパティが明示的に指定されている場合はそれに従います。

詳しくは、自動バインディング(コンストラクタ・インジェクション)自動バインディング(セッター・インジェクション)を参照してください。

コンポーネントでS2Containerを利用する

コンポーネントはS2Containerに依存しないことが望ましいのですが、コンポーネントによっては、S2Containerのメソッドを呼び出したい場合もあるでしょう。S2Container自身もcontainerという名前で、登録されているので、arg,propertyタグのボディでcontainerを指定することで、コンテナのインスタンスを取得できます。また、S2Container型のsetterメソッドを定義しておいて自動バインディングで設定することもできます。arg,propertyタグでcontainerを指定する場合は、次のようになります。

<components>
    <component class="examples.dicon.BarImpl">
        <arg>container</arg>
    </component>

    <component class="examples.dicon.FooImpl">
        <property name="foo">container</property>
    </component>
</components>

S2ContainerServlet

これまでは、Javaアプリケーションで、明示的にS2Containerを作成していましたが、Webアプリケーションの場合、誰がS2Containerを作成するのでしょうか。その目的のために次のクラスが用意されています。

- org.seasar.framework.container.servlet#S2ContainerServlet

S2ContainerServletを使うためには、web.xmlに次の項目を記述します。
src/org/seasar/framework/container/servlet/web.xmlに記述例もあります。
<servlet>
    <servlet-name>s2servlet</servlet-name>
    <servlet-class>org.seasar.framework.container.servlet.S2ContainerServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>app.dicon</param-value>
    </init-param>
    <init-param>
        <param-name>debug</param-name>
        <param-value>false</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>s2servlet</servlet-name>
    <url-pattern>/s2servlet</url-pattern>
</servlet-mapping>

configPathでメインとなるS2Container定義のパスを指定します。定義ファイルはWEB-INF/classesにおきます。S2ContainerServletは、他のサーブレットよりもはやく起動されるようにload-on-startupタグを調整してください。S2ContainerServletが起動した後は、次のメソッドでS2Containerのインスタンスを取得することができます。

- org.seasar.framework.container.factory.SingletonS2ContainerFactory#getContainer()

また、S2Containerのライフサイクルは、S2ContainerServletと連動します。debugパラメータをtrueにすると、次のようにして、稼動中にS2Containerを再起動できます。xxxはWebアプリケーションのコンテキスト名です。

http://localhost:8080/xxx/s2servlet?command=restart
			

app.diconの役割

すべてのS2Container定義のルートになる定義ファイルは、慣例でapp.diconという名前にします。app.diconにはコンポーネントの定義はしないようにしてください。通常はWEB-INF/classesにおくと良いでしょう。

<components>
    <include path="examples/foo.dicon"/>
    <include path="examples/bar.dicon"/>
          : <!-- 定義ファイルの数分記述します -->
</components>

AOPの適用

コンポーネントにAOPを適用することもできます。例えば、ArrayListにTraceInterceptorを適用したい場合次のようにします。

<components>
    <component name="traceInterceptor"
               class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
    <component class="java.util.ArrayList">
        <aspect>traceInterceptor</aspect>
    </component>
    <component class="java.util.Date">
        <arg>0</arg>
        <aspect pointcut="getTime, hashCode">traceInterceptor</aspect>
    </component>
</components>

aspectタグのボディでInterceptorの名前を指定します。pointcut属性にカンマ区切りで対象となるメソッド名を指定することができます。pointcut属性を指定しない場合は、コンポーネントが実装しているインターフェースのすべてのメソッドが対象になります。メソッド名には正規表現(JDK1.4のregex)も使えます。この定義を使うサンプルは次のようになります。

private static final String PATH =
    "examples/dicon/Aop.dicon";
S2Container container = S2ContainerFactory.create(PATH);
List list = (List) container.getComponent(List.class);
list.size();
Date date = (Date) container.getComponent(Date.class);
date.getTime();
date.hashCode();
date.toString();

実行結果は次のようになります。

BEGIN java.util.ArrayList#size()
END java.util.ArrayList#size() : 0
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
BEGIN java.util.Date#hashCode()
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
END java.util.Date#hashCode() : 0
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0

リクエストの自動バインディング

コンポーネントに対して、HttpServletRequestを自動的にバインディングすることが出来ます。そのためには、コンポーネントに、setRequest(HttpServletRequest request)のメソッドを定義します。そうすると、S2Containerが自動的にリクエストを設定します。また、次のようにFilterをweb.xmlに定義する必要があります。

<web-app>

<filter>
<filter-name>s2filter</filter-name>
<filter-class>org.seasar.framework.container.filter.S2ContainerFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>s2filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

同様にHttpServletResponse、HttpSessionもsetterメソッドを定義するだけで、自動バインディングすることが出来ます。

S2Container定義タグリファレンス

DOCTYPE

DOCTYPEは、XML宣言の次に指定します。下記のように指定してください。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="hello" class="examples.dicon.HelloConstructorInjection">
        <arg>"Hello World!"</arg>
    </component>
</components>

componentsタグ(必須)

ルートのタグになります。

namespace属性(任意)

名前空間を指定することができます。Javaの識別子として使えるものにします

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components namespace="hoge">
    ...
</components>

includeタグ(任意)

分割されたS2Containerの定義を取り込む場合に使います。

path属性(必須)

定義ファイルのパスを指定することができます。CLASSPATHで指定されているディレクトリをルートとする設定ファイルの絶対パスです。例えば、WEB-INF/classes/aaa.dicon の場合は aaa.dicon に、WEB-INF/classes/aaa/bbb/ccc.dicon の場合は aaa/bbb/ccc.dicon になりますセパレータは、WindowsでもUnixでも/です。componentタグの前に記述する必要があります。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <include path="aaa/bbb/ccc.dicon" />
</components>

componentタグ(任意)

コンポーネントを定義します。

class属性(任意)

クラスの完全限定名を指定します。ボディで、OGNL式を使ってコンポーネントを指定した場合は、class属性を省略することができます。OGNL式を使った場合にclass属性を指定すると、型チェックを行います。

name属性(任意)

名前を指定することもできます。Javaの識別子として使えるものにします。詳しくは、コンポーネントの取得を参照してください。

instance属性(任意)

S2Containerがどのようにコンポーネントのインスタンスを管理するのかを指定することができます。singleton(デフォルト)、prototype、outer、request、sessionを指定することができます。詳しくは、インスタンス管理を参照してください。

autoBinding属性(任意)

S2Containerがコンポーネントの依存関係をどのように解決するのかを指定できます。auto(デフォルト)、constructor、property、noneを指定することができます。詳しくは、自動バインディングを参照してください。

argタグ(任意)

componentタグの子タグとして使った場合は、コンストラクタの引数になります。記述した順番でコンストラクタに渡されます。 initMethodタグdestroyMethodタグの子タグとして使った場合は、メソッドの引数になります。記述した順番でメソッドに渡されます。 引数として渡される実際の値は、ボディで、OGNL式を使うか、子タグで、componentタグを使います。

propertyタグ(任意)

componentタグの子タグとして使います。プロパティとして設定される実際の値は、ボディで、OGNL式を使うか、子タグで、componentタグを使います。

name属性(必須)

プロパティ名を指定します。

initMethodタグ(任意)

componentタグの子タグとして使います。引数は、子タグで、argタグを使います。name属性を書かずに、OGNL式を使って、コンポーネントのメソッドを呼び出すこともできます。initMethodタグが定義されているコンポーネント自身を表す#self、System.outを表す#out、System.errを表す#errがinitMethodタグ内だけで有効なオブジェクトとして使えます。

name属性(任意)

メソッド名を指定します。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="java.util.HashMap">
        <initMethod name="put">
            <arg>"aaa"</arg>
            <arg>111</arg>
        </initMethod>
        <initMethod>#self.put("aaa", 111)</initMethod>
        <initMethod>#out.println("Hello")</initMethod>
    </component>
</components>

destroyMethodタグ(任意)

initMethodタグと同様です。

aspectタグ(任意)

アスペクトをコンポーネントに組み込みます。詳しくは、S2AOPのaspectタグの説明を参照してください。

descriptionタグ(任意)

componentsタグcomponentタグargタグpropertyタグの子タグとしてdescriptionタグを使うことができます。自由に説明を記述できます。

OGNL式

S2Containerでは、式言語としてOGNLを利用しています。XMLの中で、文字列で記述した内容(式)をJavaのオブジェクトに変換するためのものだと思って間違いないと思います。

  • 文字列は、"hoge"のように"で囲みます。
  • charは、'a'のように'で囲みます。
  • 数値は、123のようにそのまま記述します。
  • 論理値は、true,falseのようにそのまま記述します。
  • new java.util.Date(0)のようにクラスの完全限定名でコンストラクタを呼び出すことができます。
  • @java.lang.Math@max(1, 2)のようにstaticなメソッドを呼び出した結果を参照することができます。
  • @java.lang.String@classのようにクラスを参照できます。
  • hoge.toString()のようにコンポーネントのメソッドを呼び出した結果を参照することができます。この例は、どこかでhogeという名前のコンポーネントが定義されているという前提です。
詳しくは、OGNLのマニュアルを参照してください。

Example

以下のサンプルを実行する場合は、セットアップを行う必要があります。

コンストラクタ・インジェクション

コンストラクタ・インジェクションを使ってメッセージを表示しましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • 実装クラス(HelloConstructorInjection.java)
  • diconファイル(HelloConstructorInjection.dicon)
  • 実行クラス(HelloConstructorInjectionClient.java)
インターフェースの作成
  • showMessage()を定義します。

先ず最初はインターフェースを考えます。インターフェースと実装を分離することで、コンポーネントの利用者は、インターフェースを知っていれば実装のことは知らなくても済むようになります。また、テストの時には実装をモックに置き換えることで簡単にテストできるようになります。

package examples.dicon;

public interface Hello {

    public void showMessage();
}
実装クラスの作成
  • 引数がString型のコンストラクタを定義します。
  • showMessage()を実装します。

次はいよいよ実装です。コンストラクタでメッセージを受け取り、showMessage()で受け取ったメッセージを出力します。

package examples.dicon;

public class HelloConstructorInjection implements Hello {

    private String message;
    
    public HelloConstructorInjection(String message) {
        this.message = message;
    }
    
    public void showMessage() {
        System.out.println(message);
    }
}
diconファイルの作成
  • componentタグでコンポーネントを定義します。
  • componentタグの子タグのargタグでコンストラクタの引数値を定義します。

メッセージをコンポーネントに設定するのは、S2Containerの仕事です。定義ファイルに基づいてコンポーネントを組み立てます。

examples/dicon/HelloConstructorInjection.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="hello" class="examples.dicon.HelloConstructorInjection">
        <arg>"Hello World!"</arg>
    </component>
</components>
実行クラスの作成
package examples.dicon;

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

public class HelloConstructorInjectionClient {

    private static final String PATH =
        "examples/dicon/HelloConstructorInjection.dicon";
        
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hello hello = (Hello) container.getComponent(Hello.class);
        hello.showMessage();
        
        Hello hello2 = (Hello) container.getComponent("hello");
        hello2.showMessage();
    }
}
実行結果

argタグで指定した文字列が正しく表示されていることが確認できます。

Hello World!
Hello World!

この演習は、seasar2/src/examples/dicon以下に用意されています。

セッター・インジェクション

セッター・インジェクションを使ってメッセージを表示しましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • 実装クラス(HelloSetterInjection.java)
  • diconファイル(HelloSetterInjection.dicon)
  • 実行クラス(HelloSetterInjectionClient.java)
インターフェースの作成
  • showMessage()を定義します。

インターフェースはコンストラクタ・インジェクションの場合と同じです。プロパティに対するゲッター・メソッド、セッター・メソッドを定義する必要はありません。なぜなら、Dependency Injectionするのにコンストラクタを使うのかセッター・メソッドを使うのかは実装の問題だからです。

package examples.dicon;

public interface Hello {

    public void showMessage();
}
実装クラスの作成
  • セッター・メソッド(setMessage)を定義します。
  • showMessage()を実装します。

次は実装です。セッター・メソッドでメッセージを受け取り、showMessage()で受け取ったメッセージを出力します。

package examples.dicon;

public class HelloSetterInjection implements Hello {

    private String message;
    
    public HelloSetterInjection() {
    }
    
    public void setMessage(String message) {
        this.message = message;
    }

    public void showMessage() {
        System.out.println(message);
    }
}
diconファイルの作成
  • componentタグでコンポーネントを定義します。
  • componentタグの子タグのpropertyタグでコンポーネントのプロパティ値を定義します。

examples/dicon/HelloSetterInjection.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="examples.dicon.HelloSetterInjection">
        <property name="message">"Hello World!"</property>
    </component>
</components>
実行クラスの作成
  • S2ContainerFactory.create(String path)を呼び出してS2Containerを作成します。
  • getComponent()を使用して、S2Containerからコンポーネントを取り出します。
  • 取得したコンポーネントのメソッドを呼び出します。
package examples.dicon;

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

public class HelloSetterInjectionClient {

    private static final String PATH =
        "examples/dicon/HelloSetterInjection.dicon";
        
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hello hello = (Hello) container.getComponent(Hello.class);
        hello.showMessage();
    }
}
実行結果

propertyタグで指定した文字列が正しく表示されていることが確認できます。

Hello World!

この演習は、seasar2/src/examples/dicon以下に用意されています。

メソッド・インジェクション

メソッド・インジェクションを使ってメッセージを表示しましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • 実装クラス(HelloMethodInjection.java)
  • diconファイル(HelloMethodInjection.dicon)
  • 実行クラス(HelloMethodInjectionClient.java)
インターフェースの作成
  • showMessage()を定義します。

追加のメソッドを複数回呼び出すようなケースが代表的な使い方ですが、今回の演習では、インターフェースはコンストラクタ・インジェクションの場合と同じにしました。

package examples.dicon;

public interface Hello {

    public void showMessage();
}
実装クラスの作成
  • addMessage()を定義します。
  • showMessage()を実装します。

次は実装です。addMessage(String message)でメッセージを複数回追加して、showMessage()で受け取ったメッセージを出力します。

package examples.dicon;

public class HelloMethodInjection implements Hello {

    private StringBuffer buf = new StringBuffer();
    
    public HelloMethodInjection() {
    }
    
    public void addMessage(String message) {
        this.buf.append(message);
    }

    public void showMessage() {
        System.out.println(buf.toString());
    }
}
diconファイルの作成
  • componentタグでコンポーネントを定義します。
  • componentタグの子タグのinitMethodタグでコンポーネントのメソッドの引数値を定義します。

先ずは、argタグを使ってaddMessage(String message)に"Hello "を指定します。
次にOGNL式を使ってaddMessage(String message)に"World!"を指定します。

examples/dicon/HelloMethodInjection.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="examples.dicon.HelloMethodInjection">
        <initMethod name="addMessage">
            <arg>"Hello "</arg>
        </initMethod>
        <initMethod>#self.addMessage("World!")</initMethod>
    </component>
</components>
実行クラスの作成
  • S2ContainerFactory.create(String path)を呼び出してS2Containerを作成します。
  • getComponent()を使用して、S2Containerからコンポーネントを取り出します。
  • 取得したコンポーネントのメソッドを呼び出します。
package examples.dicon;

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

public class HelloMethodInjectionClient {

    private static final String PATH =
        "examples/dicon/HelloMethodInjection.dicon";
        
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hello hello = (Hello) container.getComponent(Hello.class);
        hello.showMessage();
    }
}
実行結果

initMethodタグで指定した文字列が正しく表示されていることが確認できます。

Hello World!

この演習は、seasar2/src/examples/dicon以下に用意されています。

自動バインディング(コンストラクタ・インジェクション)

自動バインディングでメッセージを表示してみましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • インターフェースの実装クラス(AutoHelloConstructorInjection.java)
  • diconファイル(AutoHelloConstructorInjection.dicon)
  • 実行クラス(AutoHelloConstructorInjectionClient.java)
インターフェースの作成
  • showMessage()を定義します。
package examples.dicon.autobinding;

public interface Hello {

    public void showMessage();
}
実装クラスの作成
  • 引数がMapのコンストラクタを定義します。
  • showMessage()を実装します。

コンストラクタでMapを受け取り、showMessage()でhelloをキーとしてMapから値を取得して出力します。

package examples.dicon.autobinding;

import java.util.Map;

public class AutoHelloConstructorInjection implements Hello {

    private Map map;
    
    public AutoHelloConstructorInjection(Map map) {
        this.map = map;
    }
    
    public void showMessage() {
        System.out.println(map.get("hello"));
    }
}
diconファイルの作成
  • componentタグでjava.util.Mapの実装クラスであるjava.util.HashMapをコンポーネント定義します。
  • componentタグの子タグであるinitMethodタグでHashMapにキーとして"hello"、値として"Hello World!"を定義します。
  • componentタグでAutoHelloConstructorInjectionをコンポーネント定義します。autoBinding属性にautoを指定します。
    演習であるため auto を明示的に指定していますが、autoBinding属性のデフォルト値であるため本来は省略します。

AutoHelloConstructorInjectionには、argタグが定義されていないことに注目してください。コンストラクタ・インジェクションを行う場合、argタグを定義する必要がありますが、S2Container内にMapの実装クラスが登録されていれば、S2Containerがコンテナ内を検索して自動的に引数を設定します。ただし、引数の型がインターフェースでない場合、自動バインディングはできません。

examples/dicon/autobinding/AutoHelloConstructorInjection.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="java.util.HashMap">
        <initMethod name="put">
            <arg>"hello"</arg>
            <arg>"Hello World!"</arg>
        </initMethod>
    </component>

    <component autoBinding="auto" 
               class="examples.dicon.autobinding.AutoHelloConstructorInjection"/>
</components>
実行クラスの作成
package examples.dicon.autobinding;

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

public class AutoHelloConstructorInjectionClient {

    private static final String PATH =
        "examples/dicon/autobinding/AutoHelloConstructorInjection.dicon";
        
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hello hello = (Hello) container.getComponent(Hello.class);
        hello.showMessage();
    }
}
実行結果

HashMapの値が表示されていることから、自動的にコンストラクタの引数を設定していることが確認できます。

Hello World!

この演習は、seasar2/src/examples/dicon/autobinding以下に用意されています。

自動バインディング(セッター・インジェクション)

自動バインディングでメッセージを表示してみましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • インターフェースの実装クラス(AutoHelloSetterInjection.java)
  • diconファイル(AutoHelloSetterInjection.dicon)
  • 実行クラス(AutoHelloSetterInjectionClient.java)
インターフェースの作成
  • showMessage()を定義します。
package examples.dicon.autobinding;

public interface Hello {

    public void showMessage();
}
実装クラスの作成
  • 引数がMapのコンストラクタを定義します。
  • showMessage()を実装します。

setMessage(Map map)でMapを受け取り、showMessage()でhelloをキーとしてMapから値を取得して出力します。

package examples.dicon.autobinding;

import java.util.Map;

public class AutoHelloSetterInjection implements Hello {

    private Map map;
    
    public AutoHelloSetterInjection() {
    }
    
    public void setMessage(Map map) {
        this.map = map;
    }

    public void showMessage() {
        System.out.println(map.get("hello"));
    }
}
diconファイルの作成
  • componentタグでjava.util.Mapの実装クラスであるjava.util.HashMapをコンポーネント定義します。
  • componentタグの子タグであるinitMethodタグでHashMapにキーとして"hello"、値として"Hello World!"を定義します。
  • componentタグでAutoHelloSetterInjectionをコンポーネント定義します。autoBinding属性にautoを指定します。
    演習であるため auto を明示的に指定していますが、autoBinding属性のデフォルト値であるため本来は省略します。

AutoHelloSetterInjectionには、propertyタグが定義されていないことに注目してください。セッター・インジェクションを行う場合、propertyタグで任意のプロパティを定義する必要がありますが、S2Container内にMapの実装クラスが登録されていれば、S2Containerがコンテナ内を検索して自動的にプロパティを設定します。ただし、プロパティの型がインターフェースでない場合、自動バインディングはできません。

examples/dicon/autobinding/AutoHelloSetterInjection.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="java.util.HashMap">
        <initMethod name="put">
            <arg>"hello"</arg>
            <arg>"Hello World!"</arg>
        </initMethod>
    </component>

    <component autoBinding="auto" 
               class="examples.dicon.autobinding.AutoHelloSetterInjection"/>
</components>
実行クラスの作成
package examples.dicon.autobinding;

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

public class AutoHelloSetterInjectionClient {

    private static final String PATH =
        "examples/dicon/autobinding/AutoHelloSetterInjection.dicon";
        
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Hello hello = (Hello) container.getComponent(Hello.class);
        hello.showMessage();
    }
}
実行結果

HashMapの値が表示されていることから、自動的にプロパティを設定していることが確認できます。

Hello World!

この演習は、seasar2/src/examples/dicon/autobinding以下に用意されています。