導入
クラスを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つあります。
finalにできる …notifierは一度セットしたら変更されない。「後からこっそり書き換わる」不安がない。- 必須の依存が明確 … コンストラクタの引数を見れば、そのクラスが動くために何が必要か一目で分かる。
- テストしやすい … Springを起動しなくても
new OrderService(fakeNotifier)と素のJavaで組み立てられ、単体テストが書きやすい。
フィールドインジェクションは書く量が少なく手軽に見えますが、上の3つが得られないため、実務では避けるのが定石です。
graph LR A["Notifier notifier<br/>(引数)"] -->|コンストラクタで受け取る| B["OrderService"] B -->|1度セットしたら不変| C["final フィールド"]
やってみよう
Notifier の実装が MailNotifier と SlackNotifier の2つあるとき、コンストラクタインジェクションなら OrderService のコードを1文字も変えずに差し替えられます。フィールドインジエクションだとテストコードからどう不便になるか、具体的に想像してみましょう(ヒント: new できないので、テスト時にフィールドへ値を入れる手段が限られます)。