本文へスキップ
BecomeCoder

Spring / Spring Bootコース · 第2章 Bean と DIコンテナ ― Springに管理してもらう部品たち · レッスン4

依存の渡し方 ― コンストラクタインジェクションと@Autowired

ローカル実施

導入

クラスをBeanにできたら、次は「Beanが必要とする別のBean」をどう受け取るかです。前章でコンストラクタを使う例を見ましたが、実は他にも書き方があります。なぜコンストラクタが推奨されるのかを掘り下げます。

説明

DIの注入方法には主に3種類ありますが、実務でまず選ぶべきはコンストラクタインジェクションです。比較してみましょう。

// フィールドインジェクション(非推奨)
@Service
class OrderService {
    @Autowired
    private Notifier notifier; // フィールドに直接注入される

    public void complete() {
        notifier.notify("ご注文が完了しました");
    }
}
// コンストラクタインジェクション(推奨)
@Service
class OrderService {
    private final Notifier notifier;

    @Autowired // コンストラクタが1つだけなら、実は省略できる
    public OrderService(Notifier notifier) {
        this.notifier = notifier;
    }

    public void complete() {
        notifier.notify("ご注文が完了しました");
    }
}

@Autowired は「ここにBeanを注入して」とSpringに伝える目印です。コンストラクタが1つしかないクラスでは、Spring 4.3以降 @Autowired 自体を省略できます(Springが自動で気づいてくれます)。

コンストラクタインジェクションが推奨される理由は3つあります。

  1. final にできるnotifier は一度セットしたら変更されない。「後からこっそり書き換わる」不安がない。
  2. 必須の依存が明確 … コンストラクタの引数を見れば、そのクラスが動くために何が必要か一目で分かる。
  3. テストしやすい … Springを起動しなくても new OrderService(fakeNotifier) と素のJavaで組み立てられ、単体テストが書きやすい。

フィールドインジェクションは書く量が少なく手軽に見えますが、上の3つが得られないため、実務では避けるのが定石です。

graph LR
  A["Notifier notifier<br/>(引数)"] -->|コンストラクタで受け取る| B["OrderService"]
  B -->|1度セットしたら不変| C["final フィールド"]

やってみよう

Notifier の実装が MailNotifierSlackNotifier の2つあるとき、コンストラクタインジェクションなら OrderService のコードを1文字も変えずに差し替えられます。フィールドインジエクションだとテストコードからどう不便になるか、具体的に想像してみましょう(ヒント: new できないので、テスト時にフィールドへ値を入れる手段が限られます)。