DIのインジェクション種類まとめ


なんか土日ふくめ最近何故かそういう機会が幾度かあったので、DIのインジェクション種類をまとめてみます。
DI(Dependency Injection)の種類には幾つかあって、特性が少しづつ違う(少なくとも僕はそう思ってる)ので
その違いがある程度クリアになればというのが意図です。
というわけでまとめてみました。


候補は以下の4つです。

  • セッターインジェクション
  • コンストラクタインジェクション
  • メソッドインジェクション
  • フィールドインジェクション

セッターインジェクション

セッターインジェクションは最も標準的なタイプのインジェクションです。

public void setExecutor(Executor executor) {
    this.executor = executor;
}

のようにJavaBeans仕様に従って、セッターを準備しておけばよいだけ。
基本的にはどのDIコンテナ(といっても4つか5つくらいしか知らないけど)でも動くと思う。
一番ベーシックなタイプです。迷ったら、ひとまずセッターインジェクションを僕は選択します。
これだけでも良いという案もある。実際Lucy作り始めて、当初はセッターインジェクションだけでいいんじゃない?という
意見はよく聞こえてきました。


セッターインジェクションのメリットは、IDEのサポートが受けやすいことにあります。
プロパティを準備して、EclipseなどのIDEでえい!とやれば、getter/setterを自動作成してくれます。
これでインジェクションされるポイントが作成されるので、これは便利。
というわけでインジェクションタイプの中でももっとも礼儀正しい良い子であります。


デメリットはあるセッターがあったときに、これがDIコンテナのためのものなのか、違うのかが
わかりにくいことです。セッター全てがDIコンテナのためのものではないケースが多いと思うので
そのケースではある程度何のためなのかは補足的な情報があるほうが便利です。
また、熱狂的なOO信者の人からはあんまり受けがよくなかったりもします。オブジェクトのコンストラクションのタイミングで
必要なものは全て渡しなさいという人もいらっしゃるので、多分それが理由でしょう。僕は熱狂的なOO信者ではないので、
そこまで厳密である必要はないと思っていますが。
あとは不必要にsetterとか作って不変性を壊すなとか、最近ではあんまりいないかもしれませんが、そういう人も
セッターインジェクションは好きじゃないかもしれません。

コンストラクタインジェクション


コンストラクタインジェクションはコンストラクタの引数を使って、インジェクションする方法です。
DIコンテナが出る前のDI、というよりはIoCかな、ではこの印象が強かったように思います。これは主観ですが。
なぜかというと、オブジェクトの生成時にオブジェクトが必要になるインスタンスは渡しておくべきという考え方があるからです。
手続き的にインスタンス生成して、何かを渡して初期化、そして実行よりは、インスタンス生成時に必要なものはそのタイミングで
渡すのが良いお作法だという考えです。これは何も間違っていないですが、ただ業務アプリケーション作成時に
全部がこのようにきつい制約だとテストが大変などのデメリットも出てきたりします。


というわけで今でもコンストラクタインジェクションは大体のDIコンテナで使えます。が、あまり表立っては活躍していません。
フレームワークを使うユーザはあまり使う機会が少ないはず。
逆にフレームワークを作る人には、別のフレームワークとの連携などでコンストラクタでしか渡せないようなオブジェクトがいる場合もあるので、
そういう場合に使われています。


メソッドインジェクション

メソッドインジェクションは任意のメソッド呼び出しによりコンポーネントにインジェクトする方法です。
ある任意のメソッドでオブジェクトをインジェクトできたり初期化できるのが特徴。
インジェクト時の挙動としていろいろある場合には、メソッドインジェクションが適切な場合もあります。
メソッドインジェクションは定義がちょっと難しいです。DIコンテナによって定義がそれぞれ微妙に
ずれています。
具体的にはSeasar2だとinitMethod、Springだと古くはlookup-method(だったかな?)今なら@AutoWired、
Lucyだと@Injectです。


メソッドインジェクションの機能を色々議論すると、セッターインジェクションだけで良いじゃんと思う方も結構いるかもしれません。
が、メリットはあります。
セッターインジェクションはIDEの恩恵も受けれますし良いのですが、
なにぶんインジェクトしなくてはいけない対象が何個もあると、それぞれがセッターを使ってインジェクションされます。
これは若干オーバヘッドあるかなと思います。全てのインジェクトすべきセッターでリフレクションでmethod.invoke(...)を
つかうので、複数のコンポーネントがあればその分だけコストです。
また、あるセッターがDIコンテナからインジェクトされるのか、それとも画面やロジックからセットされるのかが
わかりにくくなってしまうという特徴もあります。


またメソッドインジェクションで例えば以下のように複数同時に登録できます。

public void inject(Executor executor, ModifyTracer tracer, Updater updaer) {
    ....
    //ここで全部インジェクト
}

このケースだとインジェクトするのが一度だけで済みます。
また、インジェクトポイントが一つにまとまっていることはインジェクトのタイミングを
ソースコード上から見るうえでは重要じゃないかと思います。
(継承構造が階層的になる場合は、シングルポイントにはならず、各階層ごとになる可能性は勿論あります。)
ここだけでインジェクションされていることがわかるので、見ているクラスが何が必要かという依存関係の理解は早いはずです。


ただしデメリットもあって、メソッドインジェクションはIDEの自動生成の恩恵が受けられません。基本的には自分で書かないといけない。
上記のinjectメソッドは手書きする必要があります。ここはEclipseプラグインなどのカバーがあるといいなと思います。
もう一つ考えられるデメリットは、不必要なメソッドを公開するので、それが嫌だという人もいるかもしれません。
これは厳密にはセッターインジェクションもそうですけど。セッターのほうが違和感が少ない気がします。

フィールドインジェクション

フィールドインジェクションはフィールドに直接インジェクションしてしまうタイプです。
例えば、EJB3Seasar2のpublicフィールド、Plexusなんかもこれにあたります。

一番のメリットは、フィールドインジェクションを使うと、コードが簡潔になります。
セッターインジェクションではsetterが必要ですが、フィールドインジェクションであればフィールドにそもそも
インジェクションしてしまうので、不要です。これにより、無駄なボイラープレートコードが減ります。


デメリットは、例えばprivateフィールドにそもそもインジェクトされること自体が通常のJava開発だと
あまりありえないことなので、それが一般的には理解されにくいところでしょうか。あとはフィールドインジェクション全般に言えることだと、
ちょっとデバッグしにくいかも。最近のEclipseだと特に問題なくデバッグできそうですが。
ひとまず各末端のオブジェクト(例えばDTOなど)でいじられる可能性が無いものだけにするなど、方針や考慮をしたうえでの
他のインジェクションのタイプとの使い分けは必要になるでしょう。


まとめ


というわけで4種類のインジェクションのやり方を書いてみました。
どれも一長一短ですし、それぞれのDIコンテナで推奨する方針・得意とするやり方も違うことでしょう。
なので、お使いのDIコンテナが奨める方法との兼ね合いの部分が重要です。

ただこのように、全体を俯瞰して、こういうやり方があるのだなというのをまとめて書いておくのは
役に立つのかなと思ってまとめてみました。役に立つのは読んでくれているみなさんだけじゃなくて、
もちろん僕自身も含まれます。


出来る限りニュートラルに見たつもりですが、見方として偏っている部分があるかもしれません。
一言ある方はご指摘頂ければ幸いです。

というわけで何かの参考になれば幸いです。