この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の15日目の記事です。
こんにちは、シアトルコンサルティングの 羽田野 と申します。
この度、弊社 シアトルコンサルティング株式会社 でAdvent Calendarに参加することになりました。
TeamTech Move the WorldをMissionに掲げ、日々全力で業務に取り組んでおります!
少しでも興味を持って頂けたら下記のサイトを覗いてみてください!
コーポレートサイト
https://www.seattleconsulting.co.jp/
Wantedly
https://www.wantedly.com/companies/seattleconsulting
よろしくお願い致します!
はじめに
今回はSpringフレームワークで用いるアノテーション@Autowired
でのインジェクションの種類について見ていきます。
記事の対象としてはJavaは少しわかるけど、Springフレームワークを触り始めたくらいのレベルの方向けです。
かくいう私も初学者ではあるのでツッコミどころがあればぜひ突っ込んでもらえると嬉しいです!
そもそも@Autowired
ってなんなのよ。
簡単に言うと使いたいクラスのインスタンス化をしてくれるアノテーションです。
記述することで、クラス内のNew演算子を消せる、インスタンス化を一回で済ますことができる、といった利点があります。
public class HogeClass{
@Autowired
private HugaService hugaService;
...
}
このように記述するだけでそのクラス内でHogeServiceの処理を呼び出せるようになりました。
ただし、@Autowired
にインスタンス化を任せることができるのはSpringフレームワークが提供しているDIコンテナに登録されているクラスのみとなります。
DIコンテナへは利用したいクラスに@Component
,@Controller
,@Service
,@Repository
をつけることで登録が可能です。
※DI(依存性の注入)については記事の本筋とはずれるので割愛します。
@Autowired
によるインジェクションの種類
それでは本題の@Autowired
によるインジェクションの種類を見ていきます。
インジェクションの記法としては下記の3つがあります。
- フィールドインジェクション(非推奨)
- セッターインジェクション
- コンストラクタインジェクション(推奨)
それぞれ記法を記していきます。
フィールドインジェクション
public class HogeClass{
@Autowired
private HugaService hugaService;
private HugaHugaService hugahugaService;
...
}
こちらは見ての通り、前述の例で出した記法です。
おそらく先程あげた3つの記法の中で最も簡単にインジェクションができるため、使っている人も多いと思います。
ただし、こちらはSpringでは 非推奨 の記法となっているので注意してください。
※理由はコンストラクタインジェクションの推奨理由に後述
セッターインジェクション
public class HogeClass{
private final HugaService hugaService;
private final HugaHugaService hugaHugaService;
@Autowired
public setHugaService(HugaService hugaService){
this.hugaService = hugaService;
}
@Autowired
public setHugaHugaService(HugaHugaService hugaHugaService){
this.hugaHugaService = hugaHugaService;
}
...
}
こちらはbean設定用のsetterを@Autowired
を付与して定義する方法です。
推奨/非推奨については明言されていませんが、setterを使うという性質上、コンストラクタ呼び出し後に書き換えが可能なため、状態遷移のあるクラス/メソッドになり不安定化しやすいため、他の方法に比べるとあまり使われていないようです…
コンストラクタインジェクション
public class HogeClass{
private final HugaService hugaService;
private final HugaHugaService hugahugaService;
@Autowired
public HogeClass(HugaService hugaService){
this.hugaService = hugaService;
this.hugahugaService = hugahugaService;
}
...
}
こちらのコンストラクタインジェクションがSpringが推奨するインジェクション方法になります。
先程のフィールドインジェクションに比べると記述が増えており、なんで簡単な方じゃだめなの?と思う方もいると思います。
ここについては後ほど簡単に解説します。
なぜコンストラクタインジェクションが推奨されるのか
では、なぜコンストラクタインジェクションが推奨されるのでしょうか?
記述量を増やさずに簡単にかけてしまうのであれば、フィールドインジェクションで良いのでは?と思った方もいるのではないでしょうか。
コンストラクタインジェクションが推奨されている観点としては様々あるようですが、個人的には下記の3つが大事かなと思っています。
単一責任の原則
オブジェクト指向の言語における設計原則の一つに「SOLID原則」というものがあります。
単一責任の原則とは「SOLID原則」の中で提唱された原則の一つで、簡単に言うと「1つのクラスは1つだけの責任(機能)を持たなければならない。」という思想です。
コンストラクタインジェクションが煩雑に感じる(記述が多く見える)場合、少なくともそのクラスは**数多くの機能をインジェクションしている(=依存関係が多い)**ことになります。
つまり単一責任の原則から見ると上記のようなクラスは「一つのクラスが多くの責任を持ちすぎている」ことになるため、原則に反しています。
コンストラクタインジェクションを用いることでそのことに気づきやすくなります。
コンストラクタが不変性を持てること
コンストラクタインジェクションを行うことでフィールドをfinalで宣言することができます。
このことでイミュータブル(状態を変更することができない)なオブジェクトにしたり、必要な依存関係のみを不変にすることができます。
フィールドインジェクションの場合、final宣言はできないため依存関係は変更可能なままとなります
循環依存することを防げる
コンストラクタインジェクションでfinal宣言している場合、コンストラクタ呼び出しのタイミングでDIの設定が完了し、その後は先述の通りイミュータブルになります。
そのため、循環依存が起きている場合、アプリケーション起動時に警告が出ます。
その他2つのインジェクションについては、実際に対象のDIコンテナが呼び出されるまでは問題を検知することができません。
終わりに
自分自身、簡単にかけてしまうのでフィールドインジェクションで書いていましたが、非推奨である理由を知って、最近はコンストラクタインジェクションをするようにしてます
便利なものにはそれなりに足りない部分もあったりするので意味を考えながら使っていきたいと思いました!
今回は以上です。ありがとうございました!
参考
https://retheviper.github.io/spring/2019/12/09/spring-dependency-injection/#fn:3
https://reasonable-code.com/spring-injection-method/
https://pppurple.hatenablog.com/entry/2016/12/29/233141
https://qiita.com/baby-degu/items/d058a62f145235a0f007