FrontPage

DIコンテナって、どうなのよ!

目的

サーバサイドJavaをやっている人間であれば、下記の単語はいたるところで目にするが、今ひとつ理解できないことが多い。

  • POJO
  • DIコンテナ(軽量コンテナ、IoCコンテナ)
  • AOP

当ドキュメントは、Java技術の潮流である軽量Javaの重要な構成要素であるDIコンテナの概念を理解することを目的とする。

必要スキル

  • 基本的なJavaのコーディングが行える
  • 基礎的なオブジェクト指向の素養がある
  • デザインパターンに関して多少の知識がある
  • Eclipse3.xを扱うことが出来る

あると良いスキル

  • Apache Mavenを利用できる
  • JUnitを利用できる

DIContainer

キーワード解説

  • 軽量Java
    軽量Javaとは、J2EEにおけるEJBを重量Javaと位置付けた際の対比表現であり具体的なソリューションを指すわけではない。
    軽量JavaはDIコンテナ、POJO、AOPなどの技術を中心として構成され、EJBのような複雑な手続きを踏まずにエレガントなオブジェクト指向設計を実現するための技術の総称である。
  • POJO
    Plain Old Java Objectの略であり、いわゆる単純なJavaBeans?を表す。ただしSetterやGetterのみのDTOのみを表すわけではないことに注意。
  • DIコンテナ
    Dependency Injection Containerの略称で、依存性(Dependency)を注入(Injection)するコンテナという意味である。
    DIコンテナは個々のPOJO間の依存性を解決し、ユーザが取得するオブジェクトを組み立てる工場の役割を果たす。
    • 軽量コンテナ
      DIコンテナと同義語、こちらのほうが広義かもしれない。
    • IoCコンテナ
      DIコンテナと同義語、Inversion Of Control Containerの略称で、操作(Control)の反転(Inversion)をするコンテナという意味である。初期の頃はDIコンテナではなく、IoCコンテナと言っていた。

何をするためのものなのか?

  • 広義には
    システムにおける本来の関心事である業務ロジックと、システムを実現するためのロジックを分離するための技術である。
  • クラスレベルで表現すると
    業務ロジックのクラスと、その業務ロジックのインスタンスを生成、管理するコードを分離しクラスの責務を単純にするための技術である。
  • もっと具体的に言うと
    インスタンス生成のためのFactory、Builderパターンや、インスタンス管理のためのSingleton、Fly Weightパターン、処理を差し込むためのProxyパターンなどの業務ロジックにまったく関係ないロジックを省き、コードを単純にするための技術である。

DIコンテナを利用すると何が変わるのか?

オブジェクト間の依存性の解決

クライアントは四角形が持つ業務ロジックを利用したいが、四角形は三角形と五角形のオブジェクトに依存している下図のような場合を考えてみる。

オブジェクトの依存関係.png

これまでクライアントは下記のように三角形、五角形のオブジェクトを自分で作った後、四角形のオブジェクトにセットしてから利用していた。

四角形 quadrangle = new 四角形();
三角形 triangle = new 三角形();
五角形 pentagon = new 五角形();
quadrangle.set三角形(triangle);
quadrangle.set五角形(pentagon);
quadrangle.start業務ロジック();//業務ロジックはここだけ!

しかし、クライアントは四角形のオブジェクトの機能が利用したいのであって、四角形のオブジェクトをセットアップしたいわけではない。突き詰めれば、業務ロジック以外の部分は本来の目的とまったく関係がなく、メンテナンス性の低下とバグの増大をもたらすだけと言える。
通常、これらの醜いコードを回避するためには、FactoryやBuilderを利用してオブジェクトの生成を隠蔽してきた。ところがデザインパターンを利用するには熟練が必要だし、何よりも見通しの悪いコードになりがちである。ここでいう見通しの悪いコードとは、あらゆるFactoryがそこら中に散在しているコードや、「〜FactoryFactory?」などという悪夢のようなクラス名のことを指している。
そこでDIコンテナを利用すると、簡単に下記のようなシンプルなコードが実現できる。ここでは本来の目的である四角形のオブジェクトをDIコンテナより直接取得している。

四角形 quadrangle = (四角形)container.getComponent("四角形");
quadrangle.start業務ロジック();//業務ロジックはここだけ!

四角形の三角形と五角形に対する依存性はどのように解決されたのだろうか?
下図を見て欲しい、DIコンテナとクライアントが示されている。

DIコンテナ.png

クライアントはDIコンテナから四角形を取得しているだけであるが、その前にDIコンテナ内部で三角形と五角形が四角形にセットされているのである。つまり、依存性(三角形、五角形)の注入である。
もちろん、実際に利用する際にはその依存関係をXMLなどによって記述しておく必要があるが、これでクライアントは四角形オブジェクト以外を意識する必要がなくシンプルで見通しの良いコードが実現することができる。

シングルトンの管理

  • シングルトンパターン
    インスタンス生成を抑えたり、JVM内で常にインスタンスを共有したい場合、下記のようなシングルトンパターンが多用される。
    public class Singleton {
        //インスタンスの保持フィールド
        private static final Singleton single= new Singleton();
        //プライベートコンストラクタ
        private Singleton(){
             //初期化処理
        }
        //インスタンス取得メソッド
        public static Singleton getInstance(){
             return single;
        }
       //そのほかメソッドやフィールドなど
    }
    しかし、他のデザインパターンと同様に本質的な業務ロジックとは関係ないという問題を抱えている。また、下記にあげるような問題も発生しがちである。
    • シングルトンだらけになってしまう
      結果としてシングルトンの冗長なコードが氾濫する。
    • コンストラクタがprivateなのでJUnitテストが行いにくくなる
      シングルトン自体のテストまでは何とかできるがシングルトンを利用しているクラスの場合においてモックテストが行えないため苦労する。
  • DIコンテナにおけるシングルトン
    多くのDIコンテナではインスタンス属性も指定することが出来る。そのためシングルトン化されていないPOJOであってもインスタンス属性をシングルトンと指定すればDIコンテナ経由で取得したオブジェクトは常にシングルトンとなる。*1
    例えば、国産DIコンテナであるSeasar2ではXML設定ファイルにたいして下記のように記述する。(なおSeasar2ではinstance属性はデフォルトでsingletonなので記述しなくても良い)
    <component name="sampleSingleton" 
               class="org.makino_style.Singleton"
               instance="singleton"/>
    S2コンテナ生成後、下記のように取得する。
    Singleton single = (Singleton) container.getComponent("sampleSingleton");
    シングルトンの生成をDIコンテナに委譲することによりシングルトンパターンのコードが省かれ、クラスの責務が明確になる。また、副次的には下記のような効果も得られる。
    • あらゆる局面においてシングルトンの利用が容易になる
    • シングルトン化対象に対してインタフェースを設定することが可能になる *2
    • モックアップによる単体テストが容易になる
  • DIコンテナがシングルトンを実現する仕組み
    DIコンテナがシングルトンを実現する仕組みは、下図のようになる。
    DIコンテナ_シングルトン化.png
    DIコンテナによるシングルトン化は厳密にはシングルトンではなく、DIコンテナインスタンスに1インスタンスしか存在しないという状態である。そのため、DIコンテナインスタンスが複数存在する場合においてはシングルトン化は保障されない。*3
    つまり、DIコンテナによりシングルトンの管理を行う場合には、常に同一のDIコンテナインスタンスから取得しなければならないという条件が付く。
    それは、必然的に下記のいずれかの状態で無ければならないことを示している。
    • DIコンテナが本当のシングルトンである状態
    • DIコンテナインスタンスが本当のシングルトンに管理されている状態

単体テストが容易になる

(それだけではないが)DIコンテナを採用する大きな理由となる。
DIコンテナはここまでの説明からも理解できる通り、POJO利用時における依存性の解決インスタンス生成の管理に大きな効果を発揮する。これは、わざわざ単体テストのし難いSingletonやFactoryを利用しなくても良いと言うことを意味する。
つまり、シンプルなPOJOに対して単体テストを記述し実施することが出来るためテストの品質は上がり、工数は低下する。
例えば下図のような一般的なロジッククラスとDAOの関係を考えてみよう。

DAOとロジック.png

この図では、クライアント(サーブレットやStrutsのAction)がAuthenticationManager?を利用してユーザー認証を行っている。またAuthenticationManager?UserInfoDao?インタフェースによりUserInfoDaoImpl?を利用(つまり依存)している。通常は、この状態に対してさらにDAOのファクトリーや、AuthenticationManager?のシングルトン化のロジックが必要かもしれない。しかし、DIコンテナを利用することによりインスタンスの管理や依存性の解決は考慮する必要はない。ゆえに、単体テストとしては下記2点を行うだけでよい。

  • UserInfoDaoImpl?の単体テスト
  • AuthentiationManager?の単体テスト

まず、UserInfoDaoImpl?はRDBシステムに依存するので環境依存テストとなる。しかしAuthenticationManager?UserInfoDao?インタフェースを利用しているのでDAOの実装がなんであろうと気にする必要はない。そのためAuthenticationManager?の単体テスト時にはUserInfoDao?型で、望まれる動作をするダミー実装を利用すればロジックの単体テストが可能となる。これらは、必ずしもDIコンテナを利用せずとも可能であるが、DIコンテナを利用することを前提とするほうが簡単である。

ロジック単体テスト.png


参考

  • Inversion of ControlコンテナとDependency Injectionパターン
    「リファクタリング」の著者であるマーチン・ファウラーがInversion Of ControlやDependency Injectionとは何なのかということを懇切丁寧に説明している。かくたにさんによる邦訳も読みやすく、DIコンテナの理解には欠かせないドキュメントである。
  • Seasar2 DIコンテナのドキュメント
    国産DIコンテナの公式ドキュメント。Seasar2のためのドキュメントではあるがDIコンテナ一般にも言及しており非常に読みやすい。

Searsar2

Seasar2はひがやすを氏を中心とするSeasarファウンデーションが開発を行っている国産のDIコンテナである。詳しいことはSeasarのオフィシャルサイトを見て欲しい。
Seasar2はDIコンテナとAOPの実装であるS2コアと、S2を利用したモジュール群であるS2プロダクトからなる。特徴としては、直感的で使いやすく日本語ドキュメントが豊富という点である。なお当ドキュメントでは、Seasar2の2.2.10を利用するものとする。

  • 入手先
    下記URLより、S2コンテナをダウンロード可能である。
    http://www.seasar.org/products.html
  • 環境構築について
    基本的には、Seasar2のjarに対してクラスパスを設定するだけである。最低限必要なjarファイルとその目的を記す。各ライブラリはSeasar2のlibディレクトリ以下に存在する。演習用の環境構築に関しては、演習のための環境構築を参照して欲しい。
    s2-framework-2.2.10.jarS2コンテナ本体
    javasssist.jarAOP利用時におけるバイトコードエンハンサ
    ognl-2.6.5.jar定義ファイルにおいて利用する式言語ライブラリ
    aopalliance.jarAOP AllianceによるAOP関連の共通インタフェースライブラリ
    servlet-api.jarS2コンテナによるHttpRequest?のバインディング機能に必要とされるサーブレットAPI
    commons-logging.jarJakarta Commonsのロギングライブラリ

2分で概要

Seasar2は下記の要素からなる。

  • S2コンテナ
    Seasar2におけるDIコンテナ本体であり、オブジェクトの生成や依存性の解決を行う。クライアントはS2コンテナから登録されたオブジェクトを取得する。
  • diconファイル
    ダイコンファイルと読む。S2コンテナで管理するオブジェクトの情報やオブジェクト間の依存関係を記述しておくXMLファイルで、クラスパス上に配置される。通常、拡張子はdiconとなる。(例:s2study.dicon)

まずは、シンプルなPOJOクラスのシングルトンをS2コンテナ経由で取得する場合の一連の手順を示す。

  1. diconファイルの記述
    まずはルートクラスパス上のdiconファイル(s2study.dicon)に対して、下記のようにSimplePojoImpl?を登録する。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components21.dtd">
    <components>
       <!-- 
            この部分が登録対象
            SampleSimplePojoと言う名前でSimplePojoImplクラス
            を登録している。
        -->
       <component name="SampleSimplePojo" 
                  class="org.makino_style.study.s2.impl.SimplePojoImpl"/>
    </components>
  2. S2Containerの生成
    diconファイルに従ったS2コンテナを作るには以下のようにする。
    S2Container container = S2ContainerFactory.create("s2study.dicon");
    container.init();
  3. コンポーネントの取得
    S2コンテナから登録されたコンポーネントを取得するには以下のようにする。
    SimplePojoImpl pojo = 
    (SimplePojoImpl)container.getComponent("SampleSimplePojo");

5分で説明

  • diconファイル
    • 記述方法
      diconファイルは必ず下記のような形で記述する。
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
      "http://www.seasar.org/dtd/components21.dtd">
      <components>
         <component name="コンポーネント取得時の名称" 
                    class="生成するクラス名のフルパス"/>
      </components>
      新たにコンポーネントを追加したい場合はcomponent要素を追加していくことにより実現できる。
      例えば、org.makino_style.study.s2.impl.SimplePojoImpl?というクラスをSampleSimplePojo?という名前によりシングルトンとして取得したい場合は、component要素は下記のように記述する。
      <component name="SampleSimplePojo"
                 class="org.makino_style.study.s2.impl.SimplePojoImpl"/>
      もし、シングルトンとしてではなく取得毎に新しいインスタンスを取得したいい場合は、下記のようにinstance属性をprototypeと指定する(デフォルトではsingleton)ことにより実現可能である。
      <component name="SampleSimplePojoPrototype"
                 class="org.makino_style.study.s2.impl.SimplePojoImpl"
                 instance="prototype"/>
      コンポーネントタグについてはS2公式ドキュメントのcomponentsタグ(必須)を参照して欲しい。
  • 配置
    diconファイルはクラスパス上に配置される必要がある。配置先はルートでも、各パッケージのディレクトリ以下でも構わない。なお、パッケージ以下に配置される場合はS2コンテナ生成時にパッケージをディレクトリとして表現する必要がある。
    例えば、org.makino_style.study.s2パッケージ以下のs2study.diconの場合は、 org/makino_style/study/s2/s2study.diconと表現される。また、クラスパス直下(パッケージ無し)のs2study.diconであれば、そのままs2study.diconと表現される。
  • S2Containerの生成
    S2Containerの生成には2通りの方法がある。ひとつはS2Containerをシングルトンとして生成する方法、もうひとつはS2Containerを通常のインスタンスとして生成する方法である。どちらを利用しても構わないが、後者の場合はS2Containerインスタンスの管理を自分で行う必要がある。以下にそれぞれの生成方法を記述することになる。
    いずれの例でもdiconファイルはルートクラスパス上のs2study.diconとする。
    • シングルトンとして生成する方法
      シングルトンファクトリを初期化し、そのシングルトンファクトリを利用してS2Containerインスタンスを取得する。
      //初期化処理、初回のみ実行
      SingletonS2ContainerFactory.setConfigPath("s2study.dicon");
      SingletonS2ContainerFactory.init();
      //初期化後は下記のみで良い
      S2Container container = SingletonS2ContainerFactory.getContainer();
      SingletonS2ContainerFactory?がS2Containerインスタンスの生成と管理を行っており、S2Containerは常にシングルトンファクトリからgetContainer()して取得することになる。また、シングルトンファクトリのsetConfigPath?()とinit()は一回のみ実行すればよい。
  • 通所のインスタンスとして生成する方法
    ファクトリを利用し、指定したdiconファイルに対応する新しいS2Containerインスタンスを取得し、S2Containerインスタンスを初期化する。
    S2Container container = S2ContainerFactory.create("s2study.dicon");
    container.init();
    上記の方法で取得した場合、ファクトリによるインスタンスの管理は行われないためcreateするたびに新しいインスタンスが生成される。そのためS2Containerインスタンスの管理については、必ず自分で行う必要がある。
  • コンポーネントの取得
    • 登録したコンポーネントをそのまま取得
      S2Containerに登録されたコンポーネントのインスタンスを取得するには、下記のように行う。
      SimplePojoImpl simple = 
      (SimplePojoImpl)container.getComponent("SampleSimplePojo");
      このとき、getComponet()の引数であるSampleSimplePojo?は、先のdiconファイルのcomponent要素のname属性で指定したものである。また、取得したインスタンスはデフォルトではコンテナによりシングルトン化されているため、常に同じインスタンス*4が返される。もちろんcomponent要素のinstance属性がprototypeとなっている場合は、取得毎にインスタンスは異なる。
    • 分離原則に基づいて取得
      DIコンテナにおいて、登録したコンポーネントを実装クラスの型で取得することは望ましくない。そのため下図のように、SimplePojoImpl?はインタフェースとしてSimplePojo?を持つべきである。
      SimplePojoインタフェース.png
      これを踏まえて、S2ContainerからSimplePojo?を取得すると下記のようになる。
      SimplePojo simple = 
      (SimplePojo)container.getComponent("SampleSimplePojo");
      注目すべきは、変数の型である。つまりコンポーネントとして登録されているのはSimplePojoImpl?型であるが、取得したコンポーネントはそのインタフェースであるSimplePojo?型として利用している部分である。
      この手法により、クライアントコードは実装クラスを一切知ることなくSimplePojo?を利用し、クラス間の結合を疎にすることが可能となる。

演習のための環境構築

下記のファイルをダウンロードしEclipse用のS2練習環境を構築する。

クイックリファレンス

以降の作業で実行するコマンドをまとめて記す。ステップを追って実行したい場合は次の「構築手順」から見て欲しい。
S2Study.zipを解凍後、生成されたディレクトリ以下で下記のコマンドを実行し、eclipseでプロジェクトとしてインポートする。
ビルド、テスト実行

maven test

eclipse用の設定ファイル生成

maven eclipse

eclipseにMAVEN_REPO変数を設定(下記は1行、C:/eclipse/workspaceはeclipseのワークスペースとする。)

maven eclipse:add-maven-repo 
-Dmaven.eclipse.workspace="C:/eclipse/workspace" 

構築手順

  • ビルドツールMavenのインストール
    Mavenのインストールに関してはMavenReferenceを参照のこと
  • 解凍
    ダウンロードしたものを解凍する。解凍すると下記のようなディレクトリ構成となる。
    S2Study
     ├ src
     │ ├ conf (リソース)
     │ ├ java (Javaソース)
     │ └ test (テストコード)
     ├ project.xml (Mavenプロジェクト情報)
     ├ maven.xml (Mavenのビルドスクリプト)
     └ project.properties (Mavenプロジェクトプロパティ)
  • Mavenによるビルドとテスト
    解凍したS2Studyディレクトリ直下に移動し下記のコマンドを実行する。
    C:\S2Study>maven test
    Mavenの起動後、必要なライブラリのダウンロードやビルド、テストが実行される。 BUILD SUCCESSFULと表示されたらビルドは成功である。
  • Eclipseプロジェクト化
    eclipseはC:\eclipseにインストールされているものとする。 コンソール上で下記のコマンドを実行する。
    C:\S2Study>maven eclipse
    実行後、同じくBUILD SUCCESSFULと表示され.classpath.projectが生成されていたら成功である。
  • MAVEN_REPO変数の追加
    eclipseに対してMAVEN_REPO変数を設定しeclipseプロジェクトがMavenのダウンロードしたライブラリを参照できるようにする。
    • コンソールのみで行う方法
      コンソール上で下記のコマンドを実行する。このとき-Dオプションでeclipseのワークスペースを指定する。(*実際は1行)
      C:\S2Study>maven eclipse:add-maven-repo 
      -Dmaven.eclipse.workspace="C:/eclipse/workspace"
      • ワークスペースのディレクトリはスペース入りのディレクトリ名(例:Documents and Settings)でも問題ない。
      • -Dmaven.〜の間にはスペースは入らないので注意
    • project.propertiesを編集する方法
      project.propertiesに対して下記の1行を追加する。
      maven.eclipse.workspace=C:/eclipse/workspace
      コンソール上で下記のコマンドを実行する。
      C:\S2Study>maven eclipse:add-maven-repo
  • 生成されたディレクトリ等の確認
    これまでの手順実行後、ディレクトリは下記の状態になる。(追加)と記述されている物が生成されたファイル・ディレクトリである。
    S2Study
     ├ .settings (追加)
     │ └ org.eclipse.core.resources.prefs (追加)
     ├ src
     │ ├ conf
     │ ├ java
     │ └ test
     ├ target (追加)
     ├ .classpath (追加)
     ├ .project (追加)
     ├ project.xml
     ├ maven.xml
     └ project.properties
  • プロジェクトのインポート
    eclipse上からC:\S2Studyをプロジェクトとしてインポート(メニュー[File] - [Import...])し、Javaプロジェクトとして動作していることを確認する。
  • プロジェクトのクリーンアップ
    eclipse用の設定やmaven:testによりビルドされたファイルを削除する場合は下記を実施する。この作業でソースコードやmavenの設定ファイル、eclipseが削除されることはない。
    • maven:testによる結果のクリーンアップ
      C:\S2Study>maven clean
  • eclipse用の設定の削除
    C:\S2Study>maven eclipse:clean

演習

  • ディレクトリ構成
    演習を行うにあたって、下記のディレクトリ構造を前提として話を進める。なおeclipseプロジェクト用の設定ファイルやディレクトリ(.classpath、target等)の記述は省いてある。
    S2Study
     ├ src
     │ ├ conf
     │ │  └ s2study.dicon
     │ ├ java
     │ │  └ org/makino_style/study/s2(パッケージディレクトリ)
     │ │      ├ S2Sample.java
     │ │      ├ SimplePojo.java
     │ │      └ SimplePojoImpl.java
     │ └ test
     │     └ org/makino_style/study/s2(パッケージディレクトリ)
     │         └ S2SampleTest.java
     ├ project.xml
     ├ maven.xml
     └ project.properties
  • 演習のコーディング
    演習のコーディングはJUnitTestCase?であるS2SampleTest?.javaで行うこととする。S2SampleTest?にはテスト実行毎に呼び出されるsetUp()メソッドを下記のように実装しておく。
    protected void setUp() throws Exception {
        //S2Containerの初期化
        if(!SingletonS2ContainerFactory.hasContainer()){
            SingletonS2ContainerFactory.setConfigPath("s2study.dicon");
            SingletonS2ContainerFactory.init();
        }
    }
    ここでは、SingletonのS2Containerをsrc/conf/s2study.diconを用いて初期化しているが、hasContainer()により1度しか初期化を行わないようにしている。setUp()メソッドにより、以降のテストメソッドでは下記の1行でS2Containerを取得することが出来る。
    S2Container container = SingletonS2ContainerFactory.getContainer();
  • 演習の実行
    演習で作成したテストメソッドを実行するには、Javaパースペクティブのパッケージエクスプローラから、実行対象のテストメソッドを選択し、右クリック[Run As] - [JUnit Test]を選択する。または、コードエディタにおいて実行対象のテストメソッド上でAlt + Shift + Xを押した後、Tを押すことにより実行することが出来る。
    • QuickJUnitという選択肢
      QuickJUnitと言うeclipseプラグインがあるが、これを利用すると上記手順より簡単にJUnitが実行できるようになる。
      http://quick-junit.sourceforge.jp/

シングルトンインスタンス

既にサンプルコードが存在しているが、DIコンテナを利用して、通常のシングルトンインスタンスを取得してみる。

  • diconファイル
    src/conf/s2study.diconファイルに、下記のcomponent要素を追加する。(既に追加されていれば、しなくても良い)
    <component name="SampleSimplePojo" 
               class="org.makino_style.study.s2.impl.SimplePojoImpl"/>
    これは、org.makino_style.study.s2.SimplePojoImpl?クラスのシングルトンインスタンスをSampleSimplePojo?と言う名前で取得できると言う意味である。
  • テストコード
    public void testシンプルなオブジェクトの取得() throws Exception {
        //S2Containerの取得
        S2Container container = 
            SingletonS2ContainerFactory.getContainer();
        
        //nameによるオブジェクトの取得
        SimplePojo simplePojo1 = 
            (SimplePojo) container.getComponent("SampleSimplePojo");
        
        //検証用に取得
        SimplePojo simplePojo2 = 
            (SimplePojo) container.getComponent("SampleSimplePojo");
        
        //#Assert
        assertNotNull("取得できていること",simplePojo1);
        assertSame("シングルトンであること",simplePojo1,simplePojo2);
    }

setterによるプロパティのセット

POJOの持つsetterメソッドを利用して、あらかじめ値(プロパティ)のセットされたインスタンスを取得する。

  • diconファイル
    src/conf/s2study.diconファイルに、下記のcomponent要素を追加する。
    <component name="SimplePropertyPojo"
               class="org.makino_style.study.s2.impl.SimplePojoImpl">
        <property name="no">1</property>
        <property name="name">"シンプルPOJO"</property>
        <property name="description">"このクラスはシンプルなPOJOです。"</property>
    </component>
    ここでは、component要素の下にproperty要素を設定している。property要素のname属性がプロパティの設定先を表し、property要素のボディ部分がその値を表している。
    下記のproperty要素を例に取る。
    <property name="no">1<property>
    この場合、SimplePojoImpl?クラスのsetNoメソッドに対して1を設定すると言う意味になる。汎化した記法としては下記となる。
    <property name="フィールド名">値として設定するOGNL式</property>
    なおproperty要素のボディ部分の表記には、OGNL(Object Graph Navigation Language)という式言語が採用されている。OGNLの文法についてはSeasar2のOGNL式のドキュメントを参照されたい。
  • テストコード
    下記のコードでは、S2Containerより取得したインスタンスに適切にプロパティがセットされているかを検証している。
    public void testプロパティのセットされたオブジェクトの取得() throws Exception {
        //S2Containerの取得
        S2Container container = 
            SingletonS2ContainerFactory.getContainer();
        
        //idによる取得
        SimplePojo propPojo =
            (SimplePojo) container.getComponent("SimplePropertyPojo");
        
        //#Assert
        assertEquals(
            "noプロパティがセットされていること",
            propPojo.getNo(),1);
        assertNotNull(
            "nameプロパティがセットされていること",
            propPojo.getName());
        assertNotNull(
            "descriptionプロパティがセットされていること",
            propPojo.getDescription());
        
        //単純に値がセットされているかを確認している
        System.out.println("no: "+propPojo.getNo());
        System.out.println("name: "+propPojo.getName());
        System.out.println("description: "+propPojo.getDescription());
    }

コンストラクタによるプロパティのセット

POJOの持つコンストラクタを利用して、あらかじめ値(プロパティ)のセットされたインスタンスを取得する。

  • diconファイル
    src/conf/s2study.diconファイルに、下記のcomponent要素を追加する。
    <component name="ConstructorSimplePropertyPojo"
               class="org.makino_style.study.s2.impl.SimplePojoImpl">
        <arg>1</arg>
        <arg>"シンプルPOJO"</arg>
        <arg>"このクラスはシンプルなPOJOです。"</arg>
    </component>
    この場合、component要素の下にarg要素があるが、このarg要素がSimplePojoImpl?のコンストラクタへの引数を表現している。
    フィールドの明示的な指定は行われていないが、上から第1引数、第2引数、第3引数となっている。arg要素のボディ部分に関してはproperty要素と同様にOGNL式で表現される。汎化した記法は下記となる。
    <arg>OGNL式</arg>
  • テストコード
    下記のコードでは、S2Containerより取得したインスタンスに適切にプロパティがセットされているかを検証している。
    public void testコンストラクタによりプロパティがセットされたオブジェクトの取得()
     throws Exception {
        //S2Containerの取得
        S2Container container = 
            SingletonS2ContainerFactory.getContainer();
        
        //idによる取得
        SimplePojo propPojo =
            (SimplePojo) container.getComponent("ConstructorSimplePropertyPojo");
        
        //#Assert
        assertEquals(
            "noプロパティがセットされていること",
            propPojo.getNo(),1);
        assertNotNull(
            "nameプロパティがセットされていること",
            propPojo.getName());
        assertNotNull(
            "descriptionプロパティがセットされていること",
            propPojo.getDescription());
    }

コンポーネントのバインド

S2ではOGNL式により各コンポーネント間の参照を容易に行うことが可能である。例えば、下記の例を取るならば、employeeコンポーネントはjobTypeプロパティにおいて、employee宣言の上で登録されているjobTypeコンポーネントを手動でバインドしている。
また、手動でバインドを行う場合、バインドされるコンポーネント(employee)のconponet要素でautoBinding属性をnoneにしておく必要がある。

  • diconファイル
    <!-- バインド要素となるコンポーネント -->
    <component name="jobType"
               class="org.makino_style.study.s2.impl.JobTypeImpl">
        <property name="name">"SystemEngineer"</property>
    </component>
    
    <!-- 手動でjobTypeのコンポーネントをバインド -->
    <component name="employee"
               class="org.makino_style.study.s2.impl.EmployeeImpl"
               autoBinding="none">
        <property name="no">1</property>
        <property name="name">"Fumitaka Makino"</property>
        <!-- 
          jobTypeコンポーネントをjobTypeという
          プロパティ(setter)に設定している。
        -->
        <property name="jobType">jobType</property>
    </component>
  • テストコード
    このとき、クライアントコード上ではemployeeの取得に特殊な方法を利用する必要はなく、開発者はemployeeコンポーネントの取得に注力すればよい。
    public void test他のコンポーネントを手動でバインド() throws Exception {
        //S2Containerの取得
        S2Container container = SingletonS2ContainerFactory.getContainer();
        //社員情報コンポーネントの取得
        Employee employee = (Employee) container.getComponent("employee");
        //#Assert
        assertEquals(1, employee.getNo());
        assertEquals("Fumitaka Makino", employee.getName());
        assertSame(
            "コンテナから取得できるjobTypeオブジェクトが設定されているか",
            container.getComponent("jobType"),
            employee.getJobType());
    }

コンポーネントのオートバインド

オートバインドを行う際に注意すべき点として下記の2点が上げられる。

  • バインドされるコンポーネント(employee)のプロパティの引数はインタフェース型出なければならない。
  • 同じネームスペース内に同一のインタフェース型を持つコンポーネントが複数宣言されている場合、オートバインドはTooManyRegistrationRuntimeException?をthrowし機能しない。

EmployeeクラスのsetJobType?において、引数にjobTypeコンポーネントがバインドされるには、JobType?インタフェースを実装していなければならない。さらにJobType?インタフェースを実装しているクラスのコンポーネントは(同じネームスペース)ひとつでなければならないということである。

  • diconファイル
    <!-- バインド要素となるコンポーネント -->
    <component name="jobType"
               class="org.makino_style.study.s2.impl.JobTypeImpl">
        <property name="name">"SystemEngineer"</property>
    </component>
    
    <!-- jobTypeのコンポーネントをオートバインド -->
    <component name="employeeByAutoBind"
               class="org.makino_style.study.s2.impl.EmployeeImpl"
               autoBinding="auto">
        <property name="no">1</property>
        <property name="name">"Fumitaka Makino"</property>
    </component>
  • テストコード
    public void test他のコンポーネントを自動でバインド() throws Exception {
        //S2Containerの取得
        S2Container container = SingletonS2ContainerFactory.getContainer();
        //社員情報コンポーネントの取得
        Employee employee = 
           (Employee) container.getComponent("employeeByAutoBind");
        //#Assert
        assertEquals(1, employee.getNo());
        assertEquals("Fumitaka Makino", employee.getName());
        assertSame(
            "コンテナから取得できるjobTypeオブジェクトが設定されているか",
            container.getComponent("jobType"),
            employee.getJobType());
    }

*1 シングルトンに限らないが、マルチスレッドに対する同期化処理に関しては自分で記述しなければならない
*2 通常のシングルトンではインタフェースを設定することはシングルトンを回避するコードを追加することになり本末転倒な状態が起きる
*3 コンテナインスタンスAから取得したシングルトン化オブジェクトと、コンテナインスタンスBから取得したシングルトン化オブジェクトは同じインスタンスではないということである。
*4 ただし、同一のS2Containerインスタンスから取得した場合に限る。

添付ファイル: fileS2Study.zip 650件 [詳細] fileDAOとロジック.png 641件 [詳細] fileSimplePojoインタフェース.png 557件 [詳細] fileDIコンテナ_シングルトン化.png 786件 [詳細] fileロジック単体テスト.png 736件 [詳細] fileオブジェクトの依存関係.png 708件 [詳細] fileDIコンテナ.png 715件 [詳細]

リロード   新規 編集 凍結 差分 添付 複製 改名   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS
Last-modified: 2010-03-14 (日) 07:00:45 (2830d)