0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DIコンテナは裏側で何をしているのか? 〜@Autowiredの中身を分解する〜

0
Posted at

はじめに

前回の記事では、「なぜDIが必要なのか」を new をなくす3ステップで説明しました。

@Autowired を書けばSpringが勝手にいい感じにしてくれる

——ここまでは分かった。でも、その「いい感じ」の中で、実際に いつ・どんな順番で インスタンスが作られて、注入されているのでしょうか?

@Component を付けたクラスが、なぜか自動的にどこかに登録され、@Autowired を付けた場所に、いつの間にか実体が入っている。この「いつの間にか」を分解するのが今回のテーマです。

対象読者:

  • 前回記事(DIの基本)を読んだ人
  • @Autowired の動くタイミングがよく分からない人
  • 「Beanのライフサイクル」という言葉に馴染みがない人

1. DIコンテナの正体:ApplicationContext

これまで「DIコンテナ」と一括りに呼んでいましたが、Spring Frameworkにおいてその実体は ApplicationContext と呼ばれるオブジェクトです。

Spring Bootアプリケーションを起動すると、内部で自動的に ApplicationContext が1つ生成されます。これが、アプリ内の全ての @Component クラスの生成・管理・注入を一手に引き受ける「元締め」です。

DIコンテナがやっていることは、大きく分けると次の3フェーズです。

順番に見ていきましょう。


2. ① 収集フェーズ:誰が管理対象なのか調べる

アプリ起動時、Springはまず「どのクラスをDIコンテナで管理すべきか」を洗い出します。これを コンポーネントスキャン と呼びます。

@SpringBootApplication が付与されたクラスを起点に、同じパッケージ以下を走査し、@Component(や、それを内包する @Service @Repository @Controller など)が付いたクラスを探し出します。

このとき見つかったクラス1つひとつに対して、Springは「設計図」にあたる情報(Bean定義)を内部に作成します。この段階では、まだインスタンスは1つも作られていません。あくまで「これは後で管理すべきクラスだ」というリストアップだけが行われます。

前回の例で言えば、この段階で ClassAClassB が「Bean定義リスト」に載る、というイメージです。


3. ② 生成フェーズ:依存関係を解決しながらインスタンス化する

次にSpringは、リストアップしたBean定義をもとに、実際のインスタンスを生成していきます。

ここで重要なのが 生成順序 です。ClassAClassB に依存している場合、ClassA を先に作ってしまうと、注入すべき ClassB がまだ存在しないことになります。そのためSpringは、依存関係を解析し、依存されている側(ClassB)から先に生成する ように順序を組み立てます。

この「依存されている側から先に作る」という解決の連鎖があるため、Aがたくさんの依存を持っていたり、依存同士が複雑に絡み合っていたりすると、Springはグラフを辿るように生成順序を決めていきます。

ちなみに、ClassAClassB が互いを必要とし合っている状態を「循環参照(循環依存)」と呼びます。以前のSpringではフィールド注入を使うことでこれを強引に解決できましたが、設計の悪化を招くため、現在のSpring Boot(2.6以降)ではデフォルトでエラーになり起動しなくなりました。ここでも「コンストラクタ注入を使って、最初から循環依存が発生しないクリーンな設計にする」ことが推奨されています。


4. ③ 注入フェーズ:フィールドとコンストラクタでタイミングが違う

生成が終わったら、いよいよ注入です。ここで、前回のコラムで触れた「フィールド注入」と「コンストラクタ注入」の違いが効いてきます。

  • コンストラクタ注入:インスタンスを new する(コンストラクタを呼ぶ)と同時に、依存先が渡される。つまり 生成と注入が同時に完了する
  • フィールド注入:先にインスタンスを生成してから(ClassA のからっぽの状態を作ってから)、あとからリフレクションという仕組みを使って、こっそりフィールドに値をねじ込む。

コンストラクタ注入だと「生成された時点で必要なものが全部揃っている」ことが保証されるのに対し、フィールド注入では「一瞬、依存先が空っぽの状態」が理論上存在します。実務でコンストラクタ注入が推奨される理由の一つは、実はこの注入タイミングの違いにもあります。


5. Beanは基本1つだけ:シングルトンスコープ

もう1つ知っておきたいのが、Beanの スコープ です。

DIコンテナで管理されるBeanは、特に指定しない限り シングルトン(singleton) として扱われます。つまり、アプリ内で ClassB のインスタンスは基本的に1つしか作られず、ClassB を必要とする全てのクラスに、同じインスタンス が使い回されます。

「注入されるたびに新しいインスタンスが作られる」と誤解しがちなポイントなので、要注意です(必要であれば @Scope("prototype") で毎回生成する挙動に変えることもできます)。


6. 全体の流れをおさらい

3フェーズを1枚の図にまとめると、こうなります。

@Autowired の「いつの間にか」の正体は、起動時にDIコンテナがこの一連の処理を黙々とこなしていた結果、というわけです。


まとめ

  • DIコンテナの実体は ApplicationContext。起動時に「収集→生成→注入」の3段階を行っている
  • @Component の収集では、まずBean定義(設計図)だけが作られる
  • インスタンス生成は「依存されている側」から順番に行われる
  • コンストラクタ注入は生成と注入が同時、フィールド注入は生成後にあとから差し込まれる
  • Beanは基本シングルトン。使い回されている前提を持っておく

「DIって結局newを肩代わりしてくれるだけでしょ?」の"肩代わり"の中身が、これで少し具体的になったのではないでしょうか。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?