概要
SpringBootを触り始めた際、「DIコンテナは大事だよ!」と諸先輩方に教えていただき、
よーし、それじゃあがんばって理解するぞ!と意気込んだわけですが、
「なるほど!」と理解できるまで少し時間がかかったので、
もし同じ方がいらっしゃったら…ということで、
本記事で「DIコンテナって何?話も見えない!」の状態から
「なんとなく雰囲気はわかった!もっと詳しく調べてみる!」の状態になってもらえたら嬉しいなと思います。
必要知識
・ほんのすこーしだけJava
読者対象
・SpringBoot初学者
そもそもDIってなんぞ?
DIとは「Dependency Injection(依存性の注入)」のことです。
依存?注入?んんん?となった方、ご安心下さい。
サンプルコードを例にできるだけ簡単に説明します。
public class ClassA {
// フィールド
private ClassB classB;
// コンストラクタ
private ClassA(ClassB classB) {
this.classB = calssB;
}
// メソッド
public void methodA() {
this.classB.methodB();
}
}
●依存性について
依存とは、「あるクラスが別のクラスのフィールで持ち、そのメソッドを利用すること」です。
サンプルコードの例ですと、ClassAはClassBのインスタンスをフィールドに持っていて、
methodA()でClassBのmethodB()にアクセスしています。
もし、ClassBのmethodB()に引数を追加するとなると、
必然的にClassAのコードも変更しなければなりません。
これが「ClassAはClassBに依存している」ということになります。
●注入について
注入とは、「変数にインスタンスを代入すること」です。
サンプルコードの例ですと、コンストラクタでやっているこの部分ですね。
// コンストラクタ
private ClassA(ClassB classB) {
this.classB = calssB;
}
コンストラクタやセッター等で初期化しておかないと、classBフィールドのmethodB()を呼び出した際にエラーが発生しちゃいますよね。
●依存性の注入
では「依存」と「注入」が理解できたところで「依存性の注入」のお話です。
依存性の注入とは、「インターフェースや抽象クラスを継承したクラスのインスタンスを代入すること」です。
例えばJavaにはListというインターフェースがあります。
そのインターフェースを実装したクラスでなければ、List型の変数に代入することはできません。
Listを継承したArrayListやLinkedList等があります。
List<Object> listA = new ArrayList<>();
List<Object> listB = new LinkedList<>();
複数の実装クラスが存在する場合、以下の2つの処理を行う必要があります。
・どの実装クラスを使用するのか(依存)
・そのクラスのインスタンスを変数に入れる(注入)
つまりListにArrayListを注入するのか、LinkedListを注入するのかを決めます。
「依存」と「注入」を合わせて、「依存性の注入」ということになります。
●じゃあなぜインターフェースを使うのか?
もちろん色々と旨味はありますが、
注入するクラスの選択肢が増えることで開発、テスト、保守がしやすくなるからです。
// 実装クラスを切り替えることができる(疎結合)
List<Object> listA = new ArrayList<>();
// ArrayList以外に変更できない(密結合)
ArrayList<Object> listB = new ArrayList<>();
密結合の場合、テストする際にArrayListのデータしか使用できないですが、
疎結合の場合、Listさえ継承しておけばokなので捗りますよね。
ということで、依存性の注入っておいしいですよね、というところまではなんとなく理解していただけたと思います。
ここでようやっと本題に入っていきます。
●じゃあSpringのDIって何をしてるの?
SpringのDIは大きく2つの仕事をしています。
- DIの対象クラスを探す
- @Autowiredアノテーションが付与されている箇所にインスタンスを注入する
- インスタンスのライフサイクルの生成と破棄
そんな短く言われても…ということなので、詳しく説明していきます。
1. DIの対象クラスを探す
Spring起動時、DIの対象となるクラスを探しす。
この処理のことを「コンポーネントスキャン」と言います。
コンポーネントスキャンでは、以下のアノテーションが付与されたクラスを探します。
@Component
@Controller
@Service
@Repository
@Configuration
@RestController
@ControllerAdvice
@ManagedBean
@Named
@Mapper
@Bean
対象クラスが見つかったら、そのクラスをDIコンテナに登録してくれます。
そうです、一言でいうと、
DIコンテはとは、「DI対象のクラスを管理してくれる入れ物」、ということになります。
ちなみにDIコンテナに登録されているクラスのことを「Bean」ともいいます。
2. @Autowiredアノテーションが付与されている箇所にインスタンスを注入する
イメージとしては以下の通りです。
// Springさんお仕事前
@Autowried
private ClassB calssB;
// Springお仕事後
private ClassB calssB = new DIContainer.getClassB();
3. インスタンスのライフサイクルの生成と破棄
当然インスタンスを生成するからには破棄までも責任をもってしてくれます。
インスタンスの生成とはクラスをnewすることで、
インスタンスの破棄はnullを代入することです。
nullを代入するとガベージコレクションがメモリからインスタンスを解放してくれます。
// インスタンスの生成
ClassA classA = new ClassA();
// インスタンスの破棄
classA = null;
そしてインスタンスの破棄はスコープで指定することができます。
ここでいうスコープは、インスタンスが生存する期間をさします。
スコープを指定するためには、クラスに@Scopeアノテーションを付けます。
@Controller
@Scope("request")
public class TestController {
// ...
}
Springが用意してくれているスコープは以下の通りです。
スコープ | 説明 |
---|---|
singleton | Spring起動時にインスタンスを1つだけ生成します。 アプリケーション全体で1つのインスタンスを共有して使用します。 @Scopeアノテーションを付与しない場合は全てsingletonになります。 |
prototype | Beanを取得するたびにインスタンスが毎回生成されます。 |
session | HTTPのセッション単位でインスタンスが生成されます。 つまり、ユーザーがログインしている間だけインスタンスが存在します。 Webアプリケーションの場合のみ使用可能。 |
request | HTTPのリクエスト単位でインスタンスが生成されます。 Webアプリケーションの場合のみ使用可能。 |
globalSession | ポートレット環境におけるGrobalSession単位でインスタンスが生成されます。 ポートレットに対応したWebアプリケーションのみ使用可能。 |
application | サーブレットのコンテキスト単位でインスタンスが生成されます。 Webアプリケーションの場合のみ使用可能。 |
※参考サイト
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch04s04.html
結局DIのうまみって?
これもサンプルコードで見ていきます。
public class ClassA {
// フィールド
private ClassB classB = new ClassB();
// メソッド
public void methodA() {
this.classB.methodB();
}
}
上記のようなコードがあったとしまして、このClassAをテストしようとします。
ただ、ClassA内で直接ClassBをnewしてしまっているので、ClassBをスタブに変更することができません。
他にも、新しいクラスを作成してもそのクラスの切替が手間です。
そこでDIを使用しておけば、あら素敵、簡単にクラスの切り替えができる!
というところが、おいしいところの1つかなと思います。
最後に
本記事はあくまで取っ掛かり用として作成したので、
かなり端折っているので、「もっとこういう機能もあるよ!こういうおいしいところもあるよ!」というものがあります。
ただいっぺんに色々言われても混乱しちゃうと思うので、
冒頭にも書いた通り、まずは本記事で雰囲気を掴んでいただけると嬉しいです。