IoC · DI · Bean — Springの*心臓部*
IoC · DI · Bean — Springの*心臓部*
🎯 このレッスンを読み終えたら
このレッスンを読み終えると、以下の3つを自信を持って実践できるようになります。
- ▸✅ @Autowired が どのように Bean を注入するか(3つの方法)
- ▸✅ 同じ型の Bean が競合したときの @Qualifier / @Primary による解決
- ▸✅ @Configuration + @Bean による外部ライブラリの登録
学習目標をチェックリストとして手元に置き、すべてに答えられるようになったらレッスンを閉じましょう。
IoC(制御の逆転)— *自分で作らず、受け取る*
核心を一言で
IoC(Inversion of Control) = オブジェクトを 自分で直接作るのではなく、フレームワークが作って 自分に渡してくれる 仕組み。制御の流れが逆転した という意味。
昔のやり方 — すべてを 直接 作る
一見すっきりして見えますが、大きな問題 があります:
- ▸テストが困難:
OrderServiceだけテストしたいのに、本物のEmailServiceも動いてしまう — メールが実際に送信される - ▸変更が困難:
EmailServiceが SmsService に変わると、すべての呼び出しコード を修正しなければならない - ▸依存関係の追跡が困難: どこで誰が誰を作っているか 追跡不可能
新しいやり方 — 受け取って使う
OrderService は どこで作られたかを気にしない。ただ 受け取って使う だけです。誰が作るかは Spring の責任。
これが IoC。オブジェクト生成の制御権をフレームワークに委ねること。
なぜ良いのか
1. テストが簡単:
2. 実装の差し替えが簡単:EmailService → KakaoMessageService に変えるには、Spring の設定を一行 変えるだけ。呼び出しコードはそのまま。
3. 依存関係が明確:コンストラクタのシグネチャを見るだけで 何が必要か 一目でわかる。
IoC コンテナ — Springの工場
オブジェクトを 作り・保管し・繋げる Springの核心コンポーネントを IoC コンテナ または ApplicationContext と呼びます。
動作の流れ:
1. アプリ起動時に Spring が @Component・@Service・@Repository・@Controller が付いたクラスを すべてスキャン
2. 発見した各クラスの インスタンスを1つ作って コンテナに保管
3. 他の場所でそのオブジェクトが必要になったら、コンストラクタ・@Autowired で 注入
> 💡 コンテナに保管されたオブジェクトを Bean と呼びます。小さな単位のオブジェクトの集まり、という意味の比喩です。
まとめ
- ▸IoC = オブジェクト生成を フレームワークに委譲
- ▸メリット = テスト・差し替え・追跡がすべて容易になる
- ▸Spring コンテナ = Bean を作り接続する工場
これが Springの 最も根本的な思想。DI・AOP・トランザクションはすべて IoC の上で動作します。
DI(依存性の注入)— *3つの方法*
DI と IoC の関係
IoC のほうが大きな概念 であり、DI(Dependency Injection) はその 実現手段 のひとつです。オブジェクトが 必要とする依存性を外部から注入する 具体的な手法。
3つの注入方法
1. コンストラクタ注入(最も推奨)
メリット:
- ▸
finalが使える → 不変性の保証 - ▸オブジェクト生成時点で すべての依存性が保証される
- ▸循環参照が コンパイル時 に発見される
- ▸テスト時にモックオブジェクトを注入しやすい
Spring 4.3+ 以降、@Autowired を省略可能。Lombok の @RequiredArgsConstructor と組み合わせるとさらに簡潔になります:
2. セッター注入
メリット:任意依存性(あってもなくてもよい)を表現できる
デメリット:final が使えない。セッターが呼ばれなければ null になる。ほとんど使われない。
3. フィールド注入(非推奨)
一見簡潔に見えますが:
- ▸テストが困難(Reflection 経由でしか注入できない)
- ▸循環参照が 実行時 まで気づかない
- ▸依存性が 追加しやすすぎて 無秩序に増える
> 💡 現場の合意:常にコンストラクタ注入。フィールド注入は レガシーコード でのみ見られる。
同じ型の Bean が複数ある場合
Spring は どれを注入すればよいか わかりません。3つの解決策:
1. @Primary — デフォルトを指定:
2. @Qualifier — 明示的に選択:
3. List で受け取る — すべての実装を受け取る:
よくある落とし穴
循環参照:A が B を注入され、B が A を注入される場合。コンストラクタ注入では 起動時にエラー。設計が間違っているサインです — 共通部分を新しいクラスに切り出す のが正解。
@Autowired vs @Resource vs @Inject:Spring 標準の @Autowired だけ知っていれば十分。他の2つは Java 標準 ですが、実務ではほとんど使われません。
まとめ
- ▸コンストラクタ注入が標準。Lombok の
@RequiredArgsConstructorとの組み合わせを推奨 - ▸同じ型の Bean が複数ある場合は
@Primaryか@Qualifier - ▸循環参照は 設計上のサイン — リファクタリングが必要
Bean — *Springが管理するオブジェクト*
Bean とは何か
Spring IoC コンテナに 登録・管理されるオブジェクト が Bean です。小さな単位のオブジェクトの集まりという比喩。通常の Java オブジェクトとの違いは、Spring がその生成・ライフサイクル・注入をすべて管理する という点です。
Bean の登録方法
1. @Component 系(最も一般的)
名前は違いますが、基本的に Bean を登録する という点は同じです。意味的な区別と一部の機能差があります(@Repository は例外変換を行う)。
2. @Bean(メソッド単位)
設定クラスのメソッドの戻り値を Bean として登録:
自分のコードでは ない 外部ライブラリのクラス(RestTemplate・ObjectMapper 等)を Bean として登録する際に使用。
3. 自動設定 — Spring Boot Starter
spring-boot-starter-data-jpa のような starter 依存関係が 自動的に 多数の Bean を登録します(DataSource・EntityManager・TransactionManager 等)。
Bean スコープ — いつ新しく作るか
デフォルトは singleton — コンテナに 1つだけ 作って全員で共有。ただし他の選択肢もあります。
最もよくある落とし穴:singleton Bean に 変更されるフィールド を持つこと。マルチスレッド環境では データが壊れる。常に 不変 または 外部ストア(DB・Redis)を使いましょう。
Bean のライフサイクル
Bean は以下の段階を経ます:
1. インスタンス生成(コンストラクタ呼び出し)
2. 依存性注入(@Autowired 等)
3. 初期化(@PostConstruct または InitializingBean.afterPropertiesSet())
4. 使用
5. 破棄(@PreDestroy または DisposableBean.destroy())
@PostConstruct は頻繁に使われます — リソースの準備・キャッシュのウォームアップ等。@PreDestroy は Graceful Shutdown に重要です。
@Configuration vs @Component
どちらも Bean として登録されますが、役割が異なります。
- ▸@Configuration — 設定クラス。内部の
@Beanメソッドを プロキシでラップして 同一インスタンスの返却を保証 - ▸@Component — 通常の Bean
@Configuration 内で @Bean メソッドが 互いを呼び出す と 同一インスタンス が返ります。@Component だと毎回新しいオブジェクトが作られるバグが発生する可能性があります。
まとめ
- ▸Bean = Spring が管理するオブジェクト
- ▸登録:
@Service等のアノテーション、または@Beanメソッド - ▸デフォルトスコープは singleton(ほとんどの場合に適切)
- ▸
@PostConstruct・@PreDestroyでライフサイクルフックが可能
@Bean · @Configuration · @Qualifier · @Primary の競合解決
@Component vs @Bean — どちらをいつ使うか
- ▸@Component(@Service・@Repository・@Controller 含む)— 自分で作成したクラス に。Spring が自動スキャンします。
- ▸@Bean — 外部ライブラリのオブジェクト または 条件付き登録 が必要なとき。
@Configuration + @Bean の例
外部ライブラリのクラス(RestTemplate・ObjectMapper) は自分で @Component を付けられないため、@Bean で登録します。
同じ型の Bean が2つ — 競合
同じ型が複数 → Spring が どれを注入すればよいかわからない。
解決策1 — @Qualifier で名前を指定
Bean の名前は メソッド名 がデフォルト(cardPayment・kakaoPayment)。
解決策2 — @Primary でデフォルトを選択
どちらを使うか
- ▸デフォルト・主力実装が明確 →
@Primary - ▸状況によって異なるものを注入 →
@Qualifier - ▸どちらも使わず名前で自動マッチング も可能(
@Autowired PaymentService cardPayment;)
条件付き登録 — @ConditionalOnProperty
application.yml の payment.provider=kakao のときだけ Bean を登録します。プロファイルごとに異なる実装 をきれいに分離できます。
🤖 AI にこう依頼してみましょう
このレッスンの概念を知っていれば、AI に 具体的に 指示できます。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。
- ▸「この PaymentService の実装が2つあるので @Qualifier で分岐させてください」
- ▸「この RestTemplate を @Configuration + @Bean で登録してください」
- ▸「@ConditionalOnProperty でカカオ決済モジュールを条件付きで有効化してください」
なぜトークンが減るのか
概念を知らないと、AIの回答を受け取っても 「それは何ですか?」 と再度質問しなければなりません。その「再質問」がトークンを消費します。概念を一度理解しておけば、会話が一度で終わります。