DI(依存性の注入)は、コンポーネントを構成するインスタンスの生成と依存関係の解決をソースコードから切り離すことができます。
出典:Spring徹底入門
DIコンテナを経由したインスタンス管理には次のメリットがあります。
- インスタンスのスコープを制御できる
- インスタンスのライフサイクルを制御できる
- 共通機能を組み込める
- コンポーネント間画素結合になるため、ユニットテストがしやすい
🎃 DIに関する用語
Spring FrameworkではDIコンテナに登録するコンポーネントのことを「Bean」といいます。
また、DIコンテナからBeanを取得することを「ルックアップ」といいます。
🍄 インジェクションの種類
Spring Frameworkでは次の3つのインジェクション方法をサポートしています。
- コンストラクタインジェクション
- セッターインジェクション
- フィールドインジェクション
コンストラクタインジェクション
ここではコンストラクタインジェクションをアノテーションベースで行う方法を紹介します。
Bean定義用のアノテーションが付与されたクラスをスキャンしてDIコンテナに登録します。
自動注入のことを「オートワイヤリング」といいます。
// アノテーションを付与してコンポーネントスキャンの対象にする |
コンポーネントスキャンを有効にするにはBean定義ファイルに設定を記述します。
|
Beanの名前はクラス名の先頭を小文字にしたものです。UserRepositoryImpl
ならuserRepositoryImpl
です。
セッターインジェクション
セッターの引数に対して、依存するコンポーネントを注入できます。
|
フィールドインジェクション
インジェクトしたいフィールドに@AutowiredをアノテートすることでDIを実現できます。
|
フィールドインジェクション対象のフィールドの可読性(アクセス制御)は、指定なし(パッケージプライベート)がよいです。
ユニットテストでテスト用のフィールドを直接設定できます。
|
🚜 オートワイヤリング
オートワイヤリングには「型によるオートワイヤリング」と「名前によるオートワイヤリング」の2つがあります。
型によるオートワイヤリング
型によるオートワイヤリングはすべのインジェクションの形式で利用できます。
オートワイヤリングするBeanがない
インジェクション対象の型をもつBeanがない場合はorg.springframework.beans.factory.NoSuchBeanDefinitionException
が発生します。@Autowired(require=false)
とすれば、例外発生を回避できます。フィールド値はnull
になります。
false) // 対象のBeanがなくても例外は発生しない。nullが入る (require= |
オートワイヤリングするBeanが複数ある
インジェクション対象の型がDIコンテナに複数定義されている場合は、org.springframework.beans.factory.NoUniqueBeanDefinitionException
が発生します。@Qualifier
アノテーションでBean名を指定してください。
|
もしくはBean定義に@Primary
アノテーションをつけると、複数ある場合に優先して利用されます。
|
名前によるオートワイヤリング
名前によるオートワイヤリングは「セッターインジェクション」と「フィールドインジェクション」で利用できます。@Resource
を使ってBean名がフィールド名またはプロパティ名と一致するBeanをインジェクトします。
|
😎 コレクションやマップ型のオートワイヤリング
public Inteface IF |
この定義を使うと、次のようにオートワイヤリングできます。
|
🎂 コンポーネントスキャン
コンポーネントスキャンはクラスローダーをスキャンして特定のクラスをDIコンテナに追加する手法です。
スキャン対象には次のようなアノテーションを追加します。
アノテーション | 説明 |
---|---|
@Controller |
Controllerの役割を担うコンポーネントを示すアノテーション |
@Service |
ビジネスロジックを提供するコンポーネントを示すアノテーション |
@Repository |
データの永続化に関わるコンポーネントを示すアノテーション(O/Rマッパ) |
@Component |
上記の3つに当てはまらないコンポーネント |
🐞 Beanのスコープ
DIのメリットは、Beanの生存期間(スコープ)の管理をコンテナに任せることができる点です。スコープはScope
アノテーションで設定します。
スコープ | 説明 |
---|---|
singleton |
DIコンテナの起動時にBeanの新スタンスを生成しSingletonとして扱う。デフォルト値 |
prototype |
Beanの取得時に毎回インスタンスを生成。スレットアンセーフなBeanの場合に用いる |
session |
HTTPのセッション単位でBeanインスタンスを生成 |
request |
HTTPのリクエスト単位でBeanインスタンスを生成 |
application |
サーブレットのコンテキスト単位でBeanのインスタンスを生成 |
下図はスコープとBeanのインスタンスの関係をsingleton
とprotype
スコープの例です。
出典:Spring徹底入門
ルックアップメソッドインジェクション
Singletonのインスタンス内でprotypeスコープのインスタンスを単純にDIコンテナで使うと、Singletonが優先されます。
この解決策のひとつにLookup
アノテーションを付与する方法があります。
class UserServiceImpl implements UserService { |
Spring FrameworkがUserServiceImpl
をオーバーライドしたメソッドを作ってインスタンスを取得します。
Scoped Proxy
異なるスコープの問題を解決するもうひとつの方法にScoped Proxyがあります。
|
ルックアップとScoped Proxyの使い分け
- Scoped Proxy:
request
、session
、globalSession
スコープで利用 - ルックアップメソッドインジェクション:prototypeスコープで利用
🗻 Beanのライフサイクル
DIコンテナで管理するBeanのライフサイクルは「初期化フェーズ、利用フェーズ、終了フェーズ」の3つのフェーズで構成されます。
初期化フェーズと終了フェーズについて簡単に説明します。
初期化フェーズ
次のように処理を行います。
出典:Spring徹底入門
たとえば「Post Construct」を活用してキャッシュを作成できます。
|
終了フェーズ
DIコンテナが破棄されるタイミングで「Pre Destroy」の処理を行います。さきほどのキャッシュを削除してみます。
|
🍮 Configurationの分割
@Import
でConfigurationクラスを指定してインポートできます。
|
🎳 環境毎にConfigurationファイルを分ける
@Profile
で開発環境と本番環境でConfigurationファイルを分けることができます。
|
どのプロファイルを選択するかは起動時のオプションの-Dspring.profile.active
で指定します。
-Dspring.profiles.active=production |
指定がない場合は、spring.profiles.defaultで指定できます。