SpringでField InjectionよりConstructor Injectionが推奨される理由
SpringでField InjectionよりConstructor Injectionが推奨される理由を調べてみたメモです。
(2016/12/30) サンプルコードにfinalをつけるように修正
(2017/03/29) Immutabilityについて追記
---
家でも会社でもIntelliJを使って開発しているのですが、
Spring Bootで@Autowired(@Inject)を使うと下記のような警告が出るようになりました。
警告内容を見てみると、フィールドインジェクションは推奨されません、とのこと。
「Field injection is not recommended.」
警告の詳細を見てみると下記のように書いてあります。
「Field injection is not recommended.
Spring Team recommends: "Always use constructor based dependency injection in your beans.
Always use assertions for mandatory dependencies."」
つまり、Spring Teamは、beanのインジェクションには常にコンストラクタインジェクションを使うように、と推奨しています。
自分は今までずっとフィールド・インジェクションを使用してきました。
理由は、
- 記述が簡潔なこと
- 依存関係の追加、削除も簡単なこと
- 特に問題が発生したことが無いこと
- コンストラクタ・インジェクションを推奨する理由が分からないこと
などです。
ということでフィールド・インジェクションではなくコンストラクタ・インジェクションが推奨される理由を調べて見ました。
うまく日本語記事が発見できなかったのですが、英語の記事はたくさんあったので、
自分なりにまとめてみたら、だいたい理解することができました。
SpringでのDependency Injection
それぞれコンストラクタ・インジェクション、フィールド・インジェクション、セッター・インジェクション
を使用した簡単な例です。
コンストラクタ・インジェクション
(Spring 4.3からは単一のコンストラクタの場合、@Autowired不要になります)
ConstructorInjection.java
@Component public class ConstructorInjection { private final MyServiceA myServiceA; private final MyServiceB myServiceB; @Autowired public ConstructorInjection(MyServiceA myServiceA, MyServiceB myServiceB) { this.myServiceA = myServiceA; this.myServiceB = myServiceB; } }
フィールド・インジェクション
FieldInjection.java
@Component public class FieldInjection { @Autowired private MyServiceA myServiceA; @Autowired private MyServiceB myServiceB; }
セッター・インジェクション
SetterInjection.java
@Component public class SetterInjection { private MyServiceA myServiceA; private MyServiceB myServiceB; @Autowired public void setMyServiceA(MyServiceA myServiceA) { this.myServiceA = myServiceA; } @Autowired public void setMyServiceB(MyServiceB myServiceB) { this.myServiceB = myServiceB; } }
上の例を見てわかる通り、フィールド・インジェクションはすごく簡潔に書けます。
コード量も圧倒的に少ないです。
新しい依存関係を追加する場合も、フィールドにクラス名を追加して@Autowiredをつけるだけなのでとても簡単です。
例えば下記の様に多くの依存関係を持ったクラスの場合、
コンストラクタ・インジェクションだと記述が大変になると思ってました。
でもその考えがそもそも間違っていることが分かりました。
HasManyDependencies.java
@Component public class HasManyDependencies { private MyServiceA myServiceA; private MyServiceB myServiceB; private MyServiceC myServiceC; private MyServiceD myServiceD; private MyServiceE myServiceE; : : @Autowired public HasManyDependencies(MyServiceA myServiceA, MyServiceB myServiceB, MyServiceC myServiceC , MyServiceD myServiceD , MyServiceE myServiceE, …… ) { this.myServiceA = myServiceA; this.myServiceB = myServiceB; this.myServiceC = myServiceC; this.myServiceD = myServiceD; this.myServiceE = myServiceE; : : } }
コンストラクタ・インジェクションが推奨される観点
コンストラクタ・インジェクションが推奨される観点を下記にまとめてみました。
単一責任の原則
コンストラクタ・インジェクションが煩雑に感じる場合、クラスに多くの依存関係があることを知らせてくれています。
依存関係が多い場合、そのクラスが多くの責任を持ちすぎてる可能性があります。
これは単一責任の原則に違反します。
コンストラクタ・インジェクションを使用することでその事に気付きやすくなります。
(上のHasManyDependencies.javaの例の場合などがそうです)
依存関係の明示
そのクラスの必須の依存関係が何なのか明確になります。
必須の依存関係の場合コンストラクタ・インジェクションを使用し、
オプションの依存関係の場合、セッター・インジェクションを使用することで、
その依存関係が必須かオプションかが明確に区別できます。
再利用性(テスタビリティ)
DIコンテナで管理されるクラスは、特定のDIコンテナに依存せず、POJOであるべきです。
そうすることで、DIコンテナを使用せずにインスタンス化して単体テストが可能となり、
また、別のDIフレームワークに切り替えることも可能になります。
しかし、フィールド・インジェクションを使用している場合、
そのクラスで必要な依存関係をインスタンス化する方法がありません(リフレクションを除き)。
そのクラスをインスタンス化し、依存関係のクラスを使用すると、初期化されていないためNullPointerExceptionが発生してしまいます。
つまり、DIコンテナ以外では再利用できないことになります。
コンストラクタ・インジェクションを使用することで、インスタンス化する際に必要な依存関係を強制することが出来ます。
Immutability(不変性)
コンストラクタ・インジェクションでは、フィールドはfinalとして宣言できます。
そのため、イミュータブルなオブジェクトにしたり、必要な依存関係だけ不変にすることが出来ます。
フィールド・インジェクションの場合、フィールドはfinalでは宣言できないので依存関係は変更可能なままです。
循環依存性
コンストラクタ・インジェクションを使用する場合、循環依存が問題になる場合があります。
これはAクラスがBクラスをインジェクト、BクラスがCクラスをインジェクト、CクラスがAクラスをインジェクトする様な場合、
BeanCurrentlyInCreationExceptionが発生します。
循環依存を使用したい場合はコンストラクタ・インジェクションは使用できません。
しかし、そもそも循環依存はアンチパターンとされているため、クラス設計・分離が間違っている可能性を示してくれます。
[Tips] Lombokを使用してコンストラクタ・インジェクションを簡単に書く
Lombokを使用してコンストラクタ・インジェクションを簡単に書くことができます。
しかし、依存関係の明確さは薄れてしまう気がします。
(Spring4.3以降)
Spring4.3以降ではコンストラクタ・インジェクションで@Autowiredを省略できます。
@RequiredArgsConstructorは初期化されていないfinalなフィールドをパラメータに取るコンストラクタを生成します。
@NonNullが付与されたフィールドはnullチェックが実行され、パラメータがnullの場合はNullPointerExceptionが投げられます。
@RequiredArgsConstructor @Component public class ConstructorInjectionWithLombok { @NonNull private final MyServiceA myServiceA; @NonNull private final MyServiceB myServiceB; }
(Spring4.2以前)
Spring4.2以前はコンストラクタ・インジェクションで@Autowiredを省略できないので、
(onConstructor = @__(@Autowired))を追加する必要があります。
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @Component public class ConstructorInjectionWithLombok { @NonNull private final MyServiceA myServiceA; @NonNull private final MyServiceB myServiceB; }
という訳で、フィールド・インジェクションが推奨されない理由、コンストラクタ・インジェクションが推奨される理由が
自分なりにわかったので、今後はコンストラクタ・インジェクションを使用していきたいと思います。
【参考】
こちらの記事を参照しました。
多くの議論が書かれているので是非読んでみて下さい。
- How not to hate Spring in 2016
- Why I Changed My Mind About Field Injection?
- Oliver Gierke - Why field injection is evil
- Field Dependency Injection Considered Harmful • Spring
- I was wrong: Constructor vs. setter injection | Java Road Tripping
- Dependency Injection – Field vs Constructor vs Method | Coders Kitchen
@makingさんに指摘してもらったので修正しました。
僕も今はコンストラクタインジェクション派で、今まで書いた全部の本サンプルコードを書き直したいと思っている。ブログのコードはfinalつけたほうがいいと思う。 https://t.co/gsQgTCwQ5P
— Toshiaki Maki (@making) 2016年12月30日
終わり。
サンプルコードは下記に置きました。
github.com