はじめに
インジェクションの方法は以下の3通り
・コンストラクタインジェクション(推奨)
・フィールドインジェクション(非推奨)
・セッターインジェクション
Spring公式によるとコンストラクタインジェクションを推奨しており、フィールドインジェクションは非推奨となっている。
私がこれまで経験した開発現場ではフィールドインジェクションを利用していることが多いため、今後はコンストラクタインジェクションを積極的に利用したい。
DIについて
Dependency Injectionの略称。
Java固有の概念ではなくあらゆる言語やフレームワークで広く使われている。
大まかに以下の流れで、インスタンスの注入を行う
- SpringがDIする対象のインスタンスを探す
- @Autowiredアノテーションが付与されている箇所に、インスタンスを注入する
- インスタンスのライフサイクルの生成と破棄
本記事はインジェクションの方法に焦点を当てるため、DI詳しい解説は省く。
DIについて以下参照。
依存性の注入
インジェクションの方法
インジェクションの方法ごとに簡単なソースコードを記載した。
コンストラクタインジェクション
注入するインスタンスをコンストラクタで受け取る。
@RestController
public class ExampleController {
private final ExampleService exampleService;
// コンストラクタインジェクション(推奨)
@Autowired
public ExampleController(ExampleService exampleService) {
this.exampleService = exampleService;
}
@GetMapping("/example")
public void hoge () {
exampleService.foo();
}
}
メリット
・フィールド変数をfinalにできる。(変数を上書きさせない)
・注入するインスタンスをモックオブジェクトに置き換えることが容易で、テストコードが書きやすくなる。
デメリット
・依存するインスタンスが増えるほど、コンストラクタの記載が冗長になり可読性が落ちる。
フィールドインジェクション
注入するインスタンスをフィールド変数で受け取る。
@RestController
public class ExampleController {
// フィールドインジェクション(非推奨)
@Autowired
private ExampleService exampleService;
@GetMapping("/example")
public void hoge () {
exampleService.foo();
}
}
メリット
・コードが簡潔で可読性が高い。
デメリット
・フィールドをfinal化できない。(変数の上書きが可能。)
・注入するインスタンスをモックオブジェクトに置き換えが難しく、テストコードが書きづらい。
セッターインジェクション
注入するインスタンスをSetterで受け取る。
@RestController
public class ExampleController {
private ExampleService exampleService;
// セッターインジェクション
@Autowired
public void setExampleService (ExampleService exampleService) {
this.exampleService = exampleService;
}
@GetMapping("/example")
public void hoge () {
exampleService.foo();
}
}
メリット
・注入するインスタンスをモックオブジェクトに置き換えることが容易で、テストコードが書きやすくなる。
デメリット
・finalでないため、外部から上書きされる。
・依存するインスタンスが増えるほど、Setterの記載が冗長になり可読性が落ちる。
Lombokアノテーションで可読性を上げる
コンストラクタインジェクションを利用するうえで課題になってくるのが、注入するインスタンスの数が増えるとコードの可読性が落ちることだ。
そこでLombokアノテーションを利用することで、コンストラクタの記載を簡潔にすることができる。
// 引数付きのコンストラクタを自動生成するLombokアノテーション
@RestController
@RequiredArgsConstructor
public class ExampleController {
// コンストラクタの記載を省略。フィールド変数でインスタンスの注入を行う。
// @Autowiredも省略できる。
private final ExampleService exampleService;
@GetMapping("/example")
public void hoge () {
exampleService.foo();
}
}
フィールドインジェクションのように記載が簡潔になり、デメリットであった可読性の低下を軽減を両立できる。
元々のメリットだったフィールド変数が上書きされないことと、テストコードを書きやすさを維持できる。
したがって、インジェクションはコンストラクタインジェクションで行うべき。