その1 DIの概要を学ぶ 内容編
ページ 目次
1.DIって何だろう
いまさら何を言っているんだという感じもしますがDIって何でしょう。
DIとは「Dependency Injection」の略で、日本語だと「依存性の注入」となります。
依存性って?注入って?って感じですが頭で難しく考えるより早速プログラムを書いてみたいと思います。
2.DI無しのサンプル
DI無しのサンプルではごくごく普通のJavaのプログラムを作成します。
作成するクラスは以下のようになります。
パッケージ名 | クラス名 | 説明 |
---|---|---|
sample | SampleNormal.java | サンプルのメインクラス |
sample | SampleNormalUsed.java | サンプルクラスから使用されるクラス |
準備編で作成したプロジェクト内に新しくクラスを作成します。
SampleNormalUsed.java
package sample; /** * DI無し用のテスト用クラス. */ public class SampleNormalUsed { /** * DI無し用のテスト用メソッド. * */ public boolean chackDiSample() { System.out.println("chackDiSampleを実行しました"); return false; } }
SampleNormal.java
package sample; /** * DIを使用しない場合のメインクラス. * */ public class SampleNormal { /** * DIを使用しない場合のメインメソッド. */ public static void main(String[] args) { SampleNormalUsed snu = new SampleNormalUsed(); boolean ans = snu.chackDiSample(); if (ans) { System.out.println("結果は true"); } else { System.out.print("結果はfalse"); } } }
以上のクラスを書いたら、早速SampleNormal.javaのメインクラスを実行してみます。
結果は、以下のように表示されていると思います。
chackDiSampleを実行しました 結果はfalse
動作の流れは以下のようになります。
- SampleNormal.javaの中のmainメソッドでSampleNormalUsedをnewする。
- new したSampleNormalUsedのインスタンスでchackDiSampleメソッドを呼び出す。
- chackDiSampleの処理が行われる。
- chackDiSampleの値の戻り値によってmainメソッドの処理が実行される。
ここで注目見たいのが、SampleNormal.mainクラスの中でSampleNormalUsedをnew
という部分です。
SampleNormal.mainクラスを作成するためには、
SampleNormalUsedクラスをあらかじめ作成しておかなければなりません。
またchackDiSampleメソッドを呼び出すで対象のクラスのメソッドを呼び出しているので
そのメソッドが完成するまでmainクラスを実行することもできません。
今回はサンプルのためSampleNormalUsedは簡単に作成することができましたが
実際の場合DBへのアクセス層でまだ仕様が未確定だったりすることがあると思います。
SampleNormalUsedができてないからSampleNormal.mainもできません。ってことをしていくと
どんどんスケジュールが遅れて、そして最後には。。。
とならないためにちょっと今のサンプルを改造してみましょう。
3.DI無しのサンプルをちょっと改造したサンプル
DI無しのサンプルをちょっと改造したサンプルでは、以下のクラスを作成します。
パッケージ名 | クラス名 | 説明 |
---|---|---|
sample | SampleRemodeling | DI無しのサンプルをちょっと改造したサンプルのメインクラス |
sample | SampleRemodelingUsed | SampleRemodelingで使用するメソッドを定義するインターフェース |
sample | SampleRemodelingUsedMockImpl | SampleRemodelingUsedのメソッドを実装したMockクラス |
SampleRemodelingUsed.java
package sample; /** * SampleRemodelingで使用するメソッドを定義するインターフェース. */ public interface SampleRemodelingUsed { /** * 何かをチェックするメソッド. * */ public boolean chackDiSample(); }
SampleRemodelingUsedMockImpl.java
package sample; /** * SampleRemodelingUsedのメソッドを実装したMockクラス. */ public class SampleRemodelingUsedMockImpl implements SampleRemodelingUsed { /** * 何かをチェックするメソッドのMock実装クラス. * 常にtrueを返します. */ public boolean chackDiSample() { System.out.println("何かをチェックするメソッドのMock実装クラス - 常にtrueを返します"); return true; } }
SampleRemodeling.java
package sample; /** * DI無しのサンプルをちょっと改造したサンプルのメインクラス. */ public class SampleRemodeling { /** * DI無しのサンプルをちょっと改造したサンプルのメインメソッド. */ public static void main(String[] args) { SampleRemodelingUsed sru = new SampleRemodelingUsedMockImpl(); boolean ans = sru.chackDiSample(); if (ans) { System.out.println("結果は true"); } else { System.out.print("結果はfalse"); } } }
以上のクラスを書いたら、早速SampleRemodeling.javaのメインクラスを実行してみます。
結果は、以下のように表示されていると思います。
何かをチェックするメソッドのMock実装クラス - 常にtrueを返します 結果は true
動作の流れは以下のようになります。
- SampleRemodeling.javaの中のmainメソッドでSampleRemodelingUsedの 実装クラスであるSampleRemodelingUsedMockImplをnewする。
- newしたインスタンスでchackDiSampleメソッドを呼び出す。
- chackDiSampleの処理が行われる。
- chackDiSampleの値の戻り値によってmainメソッドの処理が実行される。
動作の流れはDI無しのサンプルとほとんど変わりませんが、クラスがひとつ増えています。
SampleRemodelingUsed.javaをインターフェースとして宣言し、SampleRemodelingUsedMockImpl.java
を実装モッククラスとしています。
これは、DI無しのサンプルで問題になっていた、SampleNormalUsed.chackDiSampleを作成するまで
メインクラスが作成できないという問題を緩和します。
たとえばDBへのアクセスロジックができてなくて、実際のDBからは値を持ってこれないけどここのクラスでは
ほしい値はこんなのだ!
(サンプルの場合だとboolean型)と決まっているのでそれをあらかじめインターフェイスとして宣言し
仮の実行メソッドとしてMockを作成しておくことでメインのクラスを作成することができます。
本番の実装クラスが完成した場合には、サンプルの例だと以下の手順で切り替えるだけで作業が完了します。
- 実装クラス(SampleRemodelingUsedImpl.java)が作成される。
- SampleRemodeling.mainで
SampleRemodelingUsed sru = new SampleRemodelingUsedMockImpl();
としているところを
SampleRemodelingUsed sru = new SampleRemodelingUsedImpl();
と変更する。
実装クラスが作成された後のクラス一覧
パッケージ名 | クラス名 | 説明 |
---|---|---|
sample | SampleRemodeling | DI無しのサンプルをちょっと改造したサンプルのメインクラス |
sample | SampleRemodelingUsed | SampleRemodelingで使用するメソッドを定義するインターフェース |
sample | SampleRemodelingUsedMockImpl | SampleRemodelingUsedのメソッドを実装したMockクラス |
sample | SampleRemodelingUsedImpl | SampleRemodelingUsedのメソッドを実装したクラス |
SampleRemodelingUsedImpl.java
package sample; /** * SampleRemodelingUsedのメソッドを実装したクラス. * * */ public class SampleRemodelingUsedImpl implements SampleRemodelingUsed { /** * 何かをチェックするメソッドの実装クラス. */ public boolean chackDiSample() { System.out.println("本来の処理が記載されます。"); return false; } }
SampleRemodeling.java
package sample; /** * DI無しのサンプルをちょっと改造したサンプルのメインクラス. */ public class SampleRemodeling { /** * DI無しのサンプルをちょっと改造したサンプルのメインメソッド. */ public static void main(String[] args) { SampleRemodelingUsed sru = new SampleRemodelingUsedImpl(); boolean ans = sru.chackDiSample(); if (ans) { System.out.println("結果は true"); } else { System.out.print("結果はfalse"); } } }
SampleRemodelingUsed.java と SampleRemodelingUsedMockImpl.javaは変更無しのため省略
書き換えた状態で実行すると以下のような結果になります。
本来の処理が記載されます。 結果はfalse
このように簡単に切り替えることができ、抽象メソッドとして宣言されているので切り替えた際にも
問題が発生しにくいです。
では、次にSeasar2を用いた場合を試してみます。
4.Seasar2を使用してのサンプル
では次にSeasar2を用いた場合のサンプルを試してみます。
作成するソース及び設定ファイルは以下のようになります。
Seasar2を使用してのサンプル クラス一覧
パッケージ名 | クラス名 | 説明 |
---|---|---|
sample.seasar2 | Seasar2SampleMain | Seasar2 のサンプルのメインクラス |
sample.seasar2 | Seasar2ServiceSample | Seasar2 のサンプルのサービスサンプル |
sample.seasar2 | Seasar2ServiceSampleImpl | Seasar2 のサンプルのサービスサンプル 実装クラス |
sample.seasar2 | sample.dicon | Seasar2にて使用する設定ファイル |
Seasar2SampleMain.java
package sample.seasar2; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; /** * Seasar2 のサンプルのメインクラス. */ public class Seasar2SampleMain { // 設定ファイルのPath private static final String PATH = "sample/seasar2/sample.dicon"; /** * Seasar2 のサンプルのメインメソッド * @param args 引数 * */ public static void main(String[] args) { // 設定ファイルを読み込む. SingletonS2ContainerFactory.setConfigPath(PATH); // 初期化する. SingletonS2ContainerFactory.init(); // コンテナを取得する. S2Container container = SingletonS2ContainerFactory.getContainer(); // コンポーネントを呼び出す. Seasar2ServiceSample s2ss = (Seasar2ServiceSample) container.getComponent(Seasar2ServiceSample.class); // 処理を実行する. s2ss.serviceSample(); // 使用したコンポーネントを廃棄する. container.destroy(); } }
Seasar2ServiceSample.java
package sample.seasar2; /** * Seasar2 のサンプルのサービスサンプル. */ public interface Seasar2ServiceSample { /** * サンプルサービス. * @return 何かの値 */ public boolean serviceSample(); }
Seasar2ServiceSampleImpl.java
package sample.seasar2; /** * Seasar2 のサンプルのサービスサンプル 実装クラス. */ public class Seasar2ServiceSampleImpl implements Seasar2ServiceSample { /** * サンプルサービス実装. * @return 何かの値 */ public boolean serviceSample() { System.out.println("サンプルクラスの作業を実行します."); return false; } }
sample.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 name="Seasar2ServiceSample" class="sample.seasar2.Seasar2ServiceSampleImpl"/> </components>
Seasar2SampleMainを実行すると以下のように表示されます。
DEBUG S2Containerを作成します。path=sample/seasar2/sample.dicon DEBUG S2Containerを作成しました。path=sample/seasar2/sample.dicon INFO Running on [ENV]product, [DEPLOY MODE]Normal Mode サンプルクラスの作業を実行します.
注 ログの出力では、本来時刻が表示されますが対象の部分を削除したので表示されていません。
/seasar2/s2-framework/src/test/resources/log4j.properties
のlog4j.appender.STDOUT.layout.ConversionPattern=%-5p %m%nとしています。
動作の流れは以下のようになります。
1.設定ファイルを読み込む.
設定ファイルである「sample.diocn」の位置を設定します。
このときclassesを基点としてpathを書きます。
サンプルの場合だと保存パッケージがsample.seasar2なので「sample/seasar2/sample.dicon」となります。
2.初期化する.
読み込んだ設定ファイルを用いてコンテナを初期化します。
3.コンテナを取得する.
2.で初期化したコンテナを取得します。
4.コンポーネントを呼び出す.
設定ファイルに記載されているコンポーネントを呼び出します。
このとき呼び出すKEYとして実装クラスの抽象メソッドを用いています。
僕の感覚ですが、IDEを用いてクラスで記載するとコード保管が効くので間違いが少なくなり便利です。
サンプルの場合 Seasar2ServiceSampleのKEYにひもづいている
sample.seasar2.Seasar2ServiceSampleImplが呼び出されています。
5.処理を実行する.
呼び出したインスタンスを用いて、serviceSample()メソッドを実行しています。
6.コンテナを廃棄する.
コンテナを廃棄します。
サンプルのメインクラス(Seasar2SampleMain.java)では、処理の実行メソッドである(Seasar2ServiceSampleImpl.java)をまったく呼び出すこと
なく実行できました。
メインクラスからすれば、処理を行ってくれるメソッド(抽象メソッドSeasar2ServiceSample.serviceSample())を呼び出すことで、
実際の処理を行うメソッドをSeasar2が仲介(sample.dicon)し、処理が行われる形になります。
仮に、
<component name="Seasar2ServiceSample" class="sample.seasar2.Seasar2ServiceSampleImpl"/>
としている部分を
<component name="Seasar2ServiceSample" class="sample.seasar2.Seasar2ServiceSampleMockImpl"/>
と修正し呼び出すことで、Javaのクラスを修正することなく処理を切り替えることができます。
僕の理解だと、javaのnewする部分(他のクラスを使用する部分)を外部に切り出し、実行するときにもらってくる。
これが「依存性の注入」=DIなのかなと思っています。
Seasar2 Topに戻る