SpringのDI(依存性の注入)に関してまとめてみた

IT Java

こんにちは。れいです。

Springに興味がある人「最近JavaでSpringフレームワークを勉強していますが、正直難しいです。特に、DI(依存性の注入)に関して分かりやすく教えてほしいです」

こういった疑問に答えます。

本記事の内容

  • SpringのDI(依存性の注入)に関してまとめてみた

DI(依存性の注入)とは

一言でいうと、インスタンス管理をしています。

大きく2つのことをやっています。

  • インスタンスの生成
  • インスタンスのライフサイクル管理

それぞれ簡単に説明します。

インスタンスの生成

DIコンテナの中でインスタンスを生成し、アプリケーションでそれらを取得して利用します。

インスタンスのライフサイクル管理

インスタンスのライフサイクル管理を簡単に実装できるようになります。

上記をDIが実施することで、開発者はクラスをnewしたり、使用後の変数を破棄する処理が不要になります。

つまり、本質的なソースコードを書くことに集中でき、コードの可読性を向上できます。

そもそも、「依存性の注入」とは何か

DIはDependency Injectionの略で、日本語では「依存性の注入」です。

しかし、「依存性の注入」と言われても良く分からないと思うので、それぞれの言葉を説明してみます。

依存性とは

ここでいう依存性とは、他のクラスを利用しているかどうかということです。

例えば、以下のクラスが存在するとします。

  • Mainクラス
  • 車クラス
  • エンジンクラスA

MainクラスでエンジンクラスAをnewしているとします。この状態が依存している状態です。

依存の問題点

では、依存していると何が問題なのでしょうか。

それは、変更に弱いということです。

例えば、エンジンクラスAの代わりに、エンジンクラスBを呼び出したいと考えた場合、Mainクラスと車クラスの両方を修正する必要があります。

両方のクラスに修正を加えないといけないので、修正漏れなどが発生してしまいます。

依存の問題点に対する解決策

上記の問題点に対する解決策として、インターフェースを使う方法があります。

つまり、以下を作ることになります。

  • Mainクラス
  • 車クラス
  • エンジンインターフェース
  • エンジンクラスA
  • エンジンクラスB

また、以下のように修正したとします。

  • Mainクラスで、エンジンインターフェースの変数にエンジンクラスのインスタンスを入れる
  • 車クラスからエンジンインターフェースを呼ぶ

上記修正により、別のエンジンクラスを利用したいと思った場合(つまり、エンジンクラスAからエンジンクラスBを使う)、Mainクラスのみを修正すれば良いため、先ほどのように両方のソースコードを修正する必要はなくなります。

注入とは

ここでいう注入の意味は、インターフェース型の変数に、インスタンスを入れることをいいます。

上記ソースコードの問題点

Mainクラスでnewをしますが、newするクラスを変更する場合、newしている行数だけ変更が発生していまいます。

例えば、newしている行が100行あれば、100行分の変更が必要になります。

上記問題点への解決策

Factoryクラスを用意することで解決します。

Factoryクラスとは、インスタンスを生成するクラスで、Javaでよく使われる技法の一つです。

Mainクラスでは、newする代わりにFactoryメソッドを呼ぶことで、メソッドの中身を変更するだけで済むようになります。

DI(依存性の注入)

DI(依存性の注入)は、上記で説明した「依存性」「注入」の解決策を簡単に行ってくれるものです。

DIの中の処理

まず、DIは以下のことを行います。

  • DIの管理対象クラスを探す(コンポーネントスキャン
  • インスタンスの作成と注入

それぞれ解説します。

DIの管理対象クラスを探す(コンポーネントスキャン)

Spring起動時に、コンポーネントスキャンが走り、DIで管理する対象のアノテーションがついているクラスを探します。

対象のアノテーションとして以下のものがあります(一部抜粋)

  • @Component
  • @Controller
  • @Service
  • @Repository

また、DIコンテナ上で管理するクラスを「Bean」と呼びます。

インスタンスの作成と注入

DI対象のクラス(Bean)を集めた後は、それらのインスタンスをDIが生成し、@Autowiredアノテーションが付いているフィールドなどに注入します。

このように、アノテーション付けるだけでインスタンスを管理できるため、Factoryクラスを作る手間が省けます。

ちなみに、@Autowiredを付けられる箇所は以下の3つです。

  • フィールド変数
  • コンストラクタへの引数
  • setterの引数

DIの実装方法

DIの実装方法は大きく以下の5つがあります。

  1. アノテーションベース
  2. JavaConfig
  3. xml
  4. JavaConfig + アノテーション
  5. xml + アノテーション

上記の説明では「アノテーションベース」を使いました。

以下では「JavaConfig」を利用する場合について、紹介しあす。

JavaConfigのDI実装方法

DIの管理対象のクラスを探す(コンポーネントスキャン)

@Configureationアノテーションをつけたクラスを用意します。

そのクラス内で、@Beanアノテーションが付いたgetterメソッドを用意します。

これにより、getterの戻り値がBeanとしてDIコンテナに登録されます。

インスタンスの生成と注入

これまでと同様に、@Autowiredをつけることで各インスタンスを取得できます。

JavaConfigを使うメリットとデメリット

メリットは、細かい設定や切り替えができる点です。

例えば、以下ができます。

  • インスタンスを生成する際に、コンストラクタなどに渡す値などを設定できる
  • 本番環境と開発環境用のJavaConfigクラスを切り替える

一方、デメリットはJavaConfigクラスを用意し、その中でメソッドを定義しないといけないことです。

定義するメソッドの数が増えれば増えるほど、開発者の負担が増します。

通常は「JavaConfig + アノテーション」を用いてDIを実装することが多いです。

DIのライフサイクル管理

通常、newでインスタンスを生成した後は、変数にnullを入れてインスタンスを破棄します。

しかし、@Autowiredを付けたフィールドにnullをわざわざ入れる必要はありません。Springが自動で実施してくれるためです。

また、サーブレットを使う場合にインスタンスをスコープに登録しますが、Springでは@Scopeアノテーションをつけることで、実施してくれます。