本項目はDIコンテナの基本的な概念の解説です。JavaやSpringの初心者向けの内容となっています。
DIコンテナはSpringの中核
サーバーサイドのJava開発をする際、Springを避けて通ることはできません。Springには数え切れないほどのプロジェクトがありますが、どのプロジェクトにも中核には必ず「DIコンテナ」があります。
DIはDependency Injection(依存性注入)の略なので、DIコンテナは「依存性注入コンテナ」となりますが、いったい依存性とは何を指し、注入とはどういう意味なのか、これから見ていきましょう。
プログラムの依存関係
ほとんどのJavaアプリケーションは複数のクラスから作られています。
それぞれのクラスは別個に独立しているわけではなく、他のクラスに依存したり依存されたりとなんらかの依存関係にあることがほとんどです。
たとえば本の検索をするWebサービスを考えてみましょう。
プログラムの流れは以下のようになります。
BookRestController
はユーザーからの要求を受け付け、BookService
に問い合わせるBookService
は中間に位置し、データを加工したり追加のビジネスロジック(キャンペーン価格を適用など)を実行BookService
はBookRepository
のメソッドを呼び出し、データベースからデータを取得BookRepository
はデータベースのパスワードなどの情報を設定ファイルから読み込み済み
上のように、アプリケーションは複数のクラスが依存しあって複雑な処理を実現します。アプリケーション全体が動作するには、依存関係が完全に解決されていないとなりません。
DIコンテナがない場合
DIコンテナがない場合、各クラスは自分が依存するクラスをインスタンスを生成してきて、それを自分のクラス内で使えるようにするにします(依存クラスの注入)。
やっていることは単純で、依存するクラスのインスタンスを生成して、自クラス内のインスタンス変数などにセットするだけです。
BookRepository
の場合は、外部の設定ファイルに依存しています。この場合もなんらかの方法で設定ファイルを読み込み、それによってデータベースのログイン情報を設定したりします。
DIコンテナがない場合の問題点
変更がしづらいコードに
このアプローチの問題点は、各クラスが依存するクラスのインスタンスを生成して注入しないといけない点にあります。せっかくクラスを分割したのに、依存関係の解決方法は各クラス内に残されてしまいます。
どうやって依存クラスをインスタンスを生成するか、また設定するか。そういった詳細は、本来は依存される側が知っていることであって、依存する側が知るべきことではありません。
上の例では大した問題には見えないかもしれません。しかし実際のアプリケーションは依存関係がもっと複雑です。ある依存クラスが変更されただけで本来の機能には何の変更もないクラスまで大幅な変更が必要になってしまいます。
テストができない低品質なコードに
また依存関係がコード内に記述されてしまうと、JUnitなどのテストの実行が困難になります。
単体テストを実行したいにどうしても依存関係が切り離せない。めんどくさいからテストしない。もしくは結合テストしか実行できずに大変な手間がかかってしまうケースもあります。
僕自身が初学者の頃にはわかっていなかったことですが、開発におけるテストの重要性を侮ってはいけません。
経験上、テストがない、もしくはテストできないコードはそもそもの設計上の問題がある可能性が高く、DIコンテナなどのフレームワークを使わずに思い思いのコーディングをしていると、設計的にも品質的にも問題の多いコードが出来上がりやすく、しかも複雑さが増すにつれて管理不能、修正するよりもゼロから作り直したほうが早くて安上がりなんてことも起こります。
そのようなコードを作ってしまうと開発者としての信頼や評価にも関わってしまうので注意が必要です。
DIコンテナによる依存関係の解決
上のような問題を解決してくれるのがDIコンテナです。ここでは依存関係がどのように解決されるかを順番に見ていきます。
1. DIコンテナが作られる
コンテナというネーミングにもあるように、まずは下のような空っぽの入れ物を思い浮かべてください。これはSpringが作ります。
このコンテナは「アプリケーションコンテキスト」とも呼ばれ、通常アプリケーションの起動時に作られます。
2. 依存関係が解析される
続いてSpringは、アプリケーション内のクラスをスキャンして、どのクラスがどのクラスに依存しているかの依存関係を解析します。 今回の例の場合、
- BookRestControllerはBookServiceに依存している
- BookRepositoryはDB設定に依存している
- BookServiceはBookRepositoryに依存している
という順序があるため、下記の順番で依存関係を解決する必要があります。 これはSpringが自動的に行うので、開発者は意識することはありません。開発者の仕事は依存関係を定義するだけです。
3. インスタンスが順番に生成される
依存関係が解析されると、Springはその順序に従ってインスタンスを生成します。Springでは各インスタンスのことを「Bean」と呼んでいます。 まずはDB設定がファイルから読み込まれます。
続いてBookRepositoryですね。 このように、順番にインスタンスが生成されていきます。すべての依存関係が解決されたら、アプリケーションはユーザーからのリクエストを処理できる状態になります。
DIがない時とある時とでは呼び出しの順序が完全に逆になっていますが、このような特徴からDIコンテナを「IoCコンテナ」と呼ぶこともあります(Inversion of Control = 制御の反転)といいます。
依存関係の定義
ここまで、依存関係が解決されるイメージを見てきましたが、Springはどのように依存関係を検出しているのでしょうか。 あえてかなり省略していますが、基本的には以下の流れになります。
- DIを利用したいクラスに
@Component
や同様のアノテーションをつける - 依存クラスのフィールドに
@Autowired
をつける
@Component public class Foo { @Autowired private Bar bar; } @Component public class Bar { }
上の例では、Foo
とBar
がDIコンテナにBeanとして登録されます。Foo
内のBar
には@Autowiredが
ついているので、SpringはここにBar
のインスタンスを注入してくれます。
設定ファイル
設定はXMLファイルを記述するか、アノテーションとJavaコードで記述する「JavaConfig」かを選べます。。 しかしトレンド的にはXMLを使った設定は敬遠されているので、新規プロジェクトではJavaConfigを使用するのがオススメです。
Spring Frameworkの書籍
僕は、Spring Frameworkを学習するにあたって以下の書籍を読みました。Spring Frameworkのコアの部分を理解するのに非常に役立ちました。 ほとんど和訳されないのが残念ですが、僕は昔からManningの「In Action」シリーズが一番好きです。本書は英語版しかないのですが、英語の勉強にもなるし、Springの情報は日本語で探そうとするよりも英語で直接仕入れらた方が手っ取り早いということも多いので、敬遠せずに読んでみるといいかもしれません。
Spring in Action: Covers Spring 4
日本語書籍のオススメはこちらですが、JDBCやAOPなど、個人的にはあまり深く読み込む必要がなさそうなものもありますが、DIやWeb関係の説明も多く価格も安いのでオススメだと思います。ちなみにKindle Unlimitedでも読めます。
オンラインコース
【Javaプログラマー必須】最強のフレームワーク、Spring。環境構築、Thymeleaf画面作成、依存性の注入まで。 (Udemy)