はじめに
何気なく使っていた @Autowiredアノテーションですが、どうやら非推奨の使い方をしていたみたいなので、アウトプットも兼ねて残せたらと思います。
※誤りありましたら、ご指摘いただけますと幸いです。
想定読者
エントリーなど初学者の方
そもそもAutowiredとは
SpringのDIコンテナから、インスタンスを注入するための目印みたいなもの。
ざっくり言うと、使いたいクラスのインスタンス化をしてくれるイメージ。
実際にはこんな感じ。
public class RamenController{
@Autowired
private RamenService ramenService;
@RequestMapping("/ramenSearch")
public String searchRamen(Integer RamenId) {
List<Ramen> ramenList = ramenService.findRamenByRamenId(RamenId);
...
}
}
上記は本当は非推奨な例ですが、これでRamenServiceクラス内の処理をRamenControllerクラス内で呼び出すことが可能となります。実際に、searchRamenメソッドの中で、RamenServiceクラス内のfindRamenByRamenIdメソッドを呼び出していますね。
DIとDIコンテナについて
まずはDIについて。
DIとは 「Dependency Injection (依存性の注入)」 の略称のことです。
なんともスッと入ってこない単語の羅列ですね。wikipediaで調べてみると、
dependencyを「依存性」と訳すのは本来の意味[1] から外れているため「依存オブジェクト注入」の用語を採用する文献も複数存在する
という記述が。「オブジェクトの注入」だとなんとなく分かる気がします。
では、上記を踏まえた上で、 「DIコンテナ」 とは何か。
簡単に言うと、オブジェクト間の依存関係を管理してくれる仕組みのこと。
Springで言うと、普段各クラスに設定している、
この辺りのアノテーションをクラスに付与することで、そのクラスのインスタンスをDIコンテナで管理する対象として指定することができるようになります。
後ほど説明する @Autowired を使用したい際は、これらのアノテーションを付与していないと、インジェクションの対象として認識しませんので、注意してください。
※詳しく知りたい方はコチラ
DIとDIコンテナについて
Autowiredを使用したDIの種類
それでは本題に入ります。
@Autowiredを使用したインジェクション(注入)には3種類あります。
フィールドインジェクション(非推奨)
普段私も使用しているのがこの形です。
記述量も少ないし、追加や削除も容易なのでこの形がベストかなと思っていました。
public class RamenController{
@Autowired
private RamenService ramenService;
...
}
セッターインジェクション
こちらはsetterを使用してDIを行う形です。
setterを使うため、コンストラクタ呼出し後に書き換えが可能となってしまうため、こちらもあまり使われない方法みたいです。
public class RamenController{
private RamenService ramenService;
@Autowired
public setRamenService(RamenService ramenService){
this.ramenService = ramenService;
}
...
}
コンストラクタインジェクション(推奨)
こちらが推奨される方法。
これだと、インスタンスを生成するタイミングで呼び出されるため、他2つと違ってfinalで定義することができます。(書き換えができない)
もう少し詳しく、次で説明します。
public class RamenController{
private final RamenService ramenService;
@Autowired
public RamenClass(RamenService ramenService){
this.ramenService = ramenService;
}
...
}
コンストラクタインジェクションを採用すべき理由
メリットは色々ありますが、主に3つ。
ちなみにここでは詳細は取り上げませんが、テストもしやすくなります。
(@SpringBootTestの記述が不要となり、単体テストが楽になる)
1. 依存コンポーネントの不変性を宣言できる
コンストラクタを使用しているので、オブジェクト生成時点でフィールドの値は初期化され、finalを使用することができます。
これによって、依存コンポーネントは**イミュータブル(不変)**であることを宣言することが出来ます。
イミュータブルであることを明示できるので、実際にコードを変更されてしまうかどうかは別として、変更するつもりがないよ、という意図をコードで表現できることが大きいと思います。
2. 循環依存を回避できる
1番で記載した通り、依存コンポーネントはイミュータブルであることを宣言できます。
そのため、循環依存が発生している場合は、アプリケーションの起動時に循環依存を検知できることもメリットです。
※循環依存:クラスAがクラスBをインジェクト、クラスBがクラスCをインジェクト、クラスCがクラスAをインジェクト、というように、依存関係が循環してしまっていること。
3. (おまけ)単一責任の原則の確認
こちらはあくまで副産物のような感じですが、コンストラクタインジェクションを使用して複数依存関係を持たせようとすると、引数や定数がどんどん増えていき、必然的にその他記述量も多くなります。
デメリットのようにも思えますが、オブジェクト指向プログラムの設計原則に挙げられる 「単一責任の原則(SOLID原則の一つ)」 に反した設計となっている可能性を示唆してくれるので、アンチパターンに気付く可能性が高まる、といったメリットもあります。
※気になる方はコチラ
単一責任の原則に関して
まとめ
普段は何気なくフィールドインジェクションでサクッと書いていましたが、確かにコード量が膨大になってくると、上記記載したメリットを享受できた方が嬉しいと感じました。
研修中はコードを書くことに必死で、設計や構造に重きを置けていなかったので、これからはアンチパターンの学習等にも時間を充て、リーダブルなコードを書くことにも意識を向けていきたいと思いました。