---
title: "IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanのスコープとライフサイクルを動くコードで解説"
url: "https://www.issoh.co.jp/tech/details/3210/"
published: 2024-07-31
updated: 2026-10-28
categories: ["Spring"]
publisher: "株式会社一創"
---

# IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanのスコープとライフサイクルを動くコードで解説

IoCコンテナとは、アプリケーションを構成するオブジェクトの生成・依存関係の解決・破棄を、開発者のコードの代わりに引き受ける仕組みです。クラスの中で `new` して部品を用意するのをやめ、「何が何を必要とするか」だけを宣言して、組み立てはコンテナに任せます。この「組み立ての主導権を手放す」考え方が制御の反転（Inversion of Control、IoC）です。

自作コンテナと、Springのスコープ・ライフサイクルの章に載せた出力は、JDK 25.0.4.1とspring-context 7.0.10で実行した結果です（それ以外のコードは説明用の断片です）。

## まとめ：IoCコンテナの要点

- IoCコンテナは、依存の連鎖をたどって下流から順にオブジェクトを生成し、注入し、使い終わったら破棄する仕組み。自作しても中核は40行ほどで書ける。
- IoCは「フレームワークがこちらのコードを呼ぶ」という広い原則で、DIはその代表的な実現手段。名前を分けたのはMartin Fowlerの2004年の記事で、IoCという呼び名が汎用すぎたため。
- Springでは`ApplicationContext`を使う。素の`BeanFactory`は`@Autowired`も`@PostConstruct`も処理しないことを実行で確認した。
- Beanの既定スコープはsingleton。singletonにprototypeを注入すると1個に固定されるので、毎回新しく欲しいときは`ObjectProvider`で取り出す。
- 初期化は`@PostConstruct`→`afterPropertiesSet()`→init-methodの順、破棄も同じ並びで呼ばれる。prototypeの破棄コールバックは呼ばれない。

## IoCコンテナとは：オブジェクトの生成と組み立てを引き受ける仕組み

### 制御の反転（IoC）：フレームワークへの呼び出し主導権の移譲

制御の反転は、Martin Fowlerが[2005年6月26日の解説「InversionOfControl」](https://martinfowler.com/bliki/InversionOfControl.html)で「フレームワークの決定的な特徴」と書いた現象です。ライブラリはこちらが呼び出しますが、フレームワークはこちらが書いたメソッドを、フレームワーク側の都合で呼び出します。Fowlerはこれを「Hollywood Principle（こちらから電話するので、電話してこないで）」とも言い換えています。

オブジェクトの組み立てに当てはめると、次の違いになります。反転前は、`OrderService`が自分で実装クラスを選んで生成します。

```
public class OrderService {
    // どの実装を使うかをOrderService自身が決めている
    private final OrderRepository repository = new JdbcOrderRepository();
}
```

この形では、テストでモックに差し替えるにも、別のデータベース実装に替えるにも、`OrderService`本体を書き換える必要があります。反転後は、必要な部品をコンストラクタで受け取るだけにして、どの実装を渡すかは外側に任せます。

```
public class OrderService {
    private final OrderRepository repository;

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}
```

この「外側」を自動化したものがIoCコンテナです。クラスが数百になると、生成順と受け渡しを手で書くコード自体が大きくなるため、それを肩代わりさせます。

### 40行で作る最小のIoCコンテナ：コンテナが内部でしていること

コンテナの中身はリフレクション（実行時にクラスの構造を読み取る仕組み）で書けます。次のコードは、コンストラクタの引数の型を読み、その型のオブジェクトを先に作ってから本体を生成し、作ったものを1個だけ保持して使い回す、最小のIoCコンテナです。リフレクションそのものの仕組みは[リフレクションとは？実行時にクラス情報を操る仕組みを言語横断で解説](/tech/details/15262/)で扱っています。

```
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class MiniContainer {
    private final Map<Class<?>, Object> singletons = new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T> T getBean(Class<T> type) {
        Object cached = singletons.get(type);
        if (cached != null) return (T) cached;
        try {
            Constructor<?> ctor = type.getDeclaredConstructors()[0];
            Class<?>[] paramTypes = ctor.getParameterTypes();
            Object[] args = new Object[paramTypes.length];
            for (int i = 0; i < paramTypes.length; i++) {
                // 依存を先に解決する
                args[i] = getBean(paramTypes[i]);
            }
            T bean = (T) ctor.newInstance(args);
            singletons.put(type, bean);
            System.out.println("生成: " + type.getSimpleName());
            return bean;
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException(e);
        }
    }

    static class OrderRepository {}
    static class MailSender {}
    static class OrderService {
        final OrderRepository repo; final MailSender mail;
        OrderService(OrderRepository repo, MailSender mail) { this.repo = repo; this.mail = mail; }
    }

    public static void main(String[] args) {
        MiniContainer c = new MiniContainer();
        OrderService s1 = c.getBean(OrderService.class);
        OrderService s2 = c.getBean(OrderService.class);
        System.out.println("同じインスタンスか: " + (s1 == s2));
        System.out.println("repoは共有か: " + (s1.repo == c.getBean(OrderRepository.class)));
    }
}
```

`OrderService`を1回要求しただけで、依存先の2つが先に生成され、2回目以降は同じインスタンスが返っています。

```
生成: OrderRepository
生成: MailSender
生成: OrderService
同じインスタンスか: true
repoは共有か: true
```

依存の連鎖をたどる生成、型による注入、生成済みインスタンスの使い回し（singleton）。IoCコンテナの骨格はこの3点です。実用のコンテナはここに、同じ型の実装が複数あるときの選び分け、寿命（スコープ）の種類、初期化と破棄のコールバック、循環依存の検出、AOPのためのプロキシ差し込みを積み上げています。この自作版は循環依存があると再帰が止まらず、インタフェース型の引数も解決できません。自作せず既存のコンテナを使うのは、この積み上げ分を保守しないためです。

## IoCとDI（依存性注入）の違い：Fowlerが名前を分けた理由

「IoCコンテナ」と「DIコンテナ」は、いまはほぼ同じものを指します。呼び名が2つあるのは歴史の名残です。2000年代前半、Javaの軽量コンテナ（PicoContainerやSpring）は自らを「Inversion of Control」のコンテナと呼んでいました。Martin Fowlerは[2004年1月23日の記事「Inversion of Control Containers and the Dependency Injection pattern」](https://martinfowler.com/articles/injection.html)で、IoCは「あまりに汎用的な用語で、人を混乱させる」として、IoCの支持者たちと議論のうえ、部品を外から差し込むこのパターンを「Dependency Injection」と名付けました。

IoCは「制御の主導権がフレームワーク側にある」という広い原則で、イベントループやテンプレートメソッドも含みます。DIは、そのうち依存オブジェクトを外から差し込むことに絞った手法です。コンストラクタ注入・セッター注入の書き分けや、同じ型のBeanが複数あるときの`@Primary`・`@Qualifier`は[SpringのDI（依存性注入）とは？仕組みと注入方法を動くコードでわかりやすく解説](/tech/details/2303/)で扱っています。

名前が似ている依存性逆転の原則（DIP）は、上位モジュールも下位モジュールも抽象（インタフェース）に依存させるという設計原則で、IoCともDIとも別物です。DIはDIPを満たす構成を組み立てやすくしますが、DIを使ってもDIPを満たすとは限りません。両者の切り分けは[依存性逆転の原則（DIP）とは？意味・違反コードの直し方とDI（依存性の注入）との違い](/tech/details/2761/)で整理しています。

### Service LocatorとDIの違い：業務コードのコンテナ依存

Fowlerの2004年の記事は、DIの対抗馬としてService Locatorを並べています。Service Locatorは、クラスが必要な部品を自分でレジストリに問い合わせて取り出す方式です。Springの[公式リファレンスの導入節](https://docs.spring.io/spring-framework/reference/core/beans/introduction.html)も、IoCコンテナの注入は「Beanが直接の生成やService Locatorパターンで依存を自分で探しにいくことの反対（それゆえInversion of Controlという名前）」だと説明しています。

Springでこの違いが表に出るのが`ApplicationContext.getBean()`です。業務クラスの中で`context.getBean(OrderRepository.class)`と書くと、コンテナを使っていても中身はService Locatorになり、そのクラスはSpringのAPIに依存し、テストでは`ApplicationContext`ごとスタブを用意しないと動かせなくなります。`getBean`を呼んでよいのは、`main`メソッドやテストの起点など、コンテナの外から最初の1個を取り出す場所だけにします。なお、Fowler自身は記事の結論で、どちらを選ぶかより「サービスの設定と利用を分離する」原則の方が重要だと書いています。

## Spring IoCコンテナの実体：BeanFactoryとApplicationContext

Springで「IoCコンテナ」と呼ばれる本体は、`org.springframework.beans.factory.BeanFactory`と、それを継承した`org.springframework.context.ApplicationContext`です。`BeanFactory`がBeanの生成と組み立ての基盤を持ち、`ApplicationContext`はそこにAOPとの統合、国際化メッセージ、イベント配信、Web用のコンテキストを足しています。

### BeanFactoryとApplicationContextの機能差

[Spring公式リファレンスの「BeanFactory or ApplicationContext?」](https://docs.spring.io/spring-framework/reference/core/beans/beanfactory.html)は、両者の機能差を次の表で示し、「特別な理由がない限りApplicationContextを使う」としています。素の`BeanFactory`を選ぶ理由として挙げられているのは、Beanの処理を完全に自分で制御したい場面です。

| 機能                            | BeanFactory | ApplicationContext |
| ----------------------------- | ----------- | ------------------ |
| Beanの生成と依存の組み立て               | あり          | あり                 |
| ライフサイクル管理の統合                  | なし          | あり                 |
| BeanPostProcessorの自動登録        | なし          | あり                 |
| BeanFactoryPostProcessorの自動登録 | なし          | あり                 |
| 国際化メッセージ（MessageSource）       | なし          | あり                 |
| イベント配信（ApplicationEvent）      | なし          | あり                 |

実装クラスは、アノテーション設定なら`AnnotationConfigApplicationContext`、素の`BeanFactory`なら`DefaultListableBeanFactory`です。Spring Bootでは`SpringApplication.run()`が`ApplicationContext`を組み立てて返します。

### 素のBeanFactory：@Autowiredと@PostConstructの処理条件

表の「BeanPostProcessorの自動登録：なし」は、実務では「アノテーションが黙って無視される」形で現れます。`@Autowired`を処理する`AutowiredAnnotationBeanPostProcessor`も、`@PostConstruct`を処理する仕組みもBeanPostProcessorとして実装されているためです。次の章で使う`Life`クラス（セッターに`@Autowired`、メソッドに`@PostConstruct`、さらに`InitializingBean`を実装）を、両方のコンテナに登録して比べました。

| 実行結果（spring-context 7.0.10）    | DefaultListableBeanFactory | AnnotationConfigApplicationContext |
| ------------------------------ | -------------------------- | ---------------------------------- |
| singletonの生成タイミング              | getBean()を呼んだとき            | 起動（refresh）時                       |
| @Autowiredのセッター                | 呼ばれない                      | 呼ばれる                               |
| @PostConstruct・@PreDestroy     | 呼ばれない                      | 呼ばれる                               |
| afterPropertiesSet()・destroy() | 呼ばれる                       | 呼ばれる                               |

素の`DefaultListableBeanFactory`で出力されたのは「1 コンストラクタ」「4 afterPropertiesSet」「7 destroy」の3行だけでした。例外も警告も出ないまま注入が抜け落ちるので、設定ミスと見分けがつきません。公式リファレンスも、この状況は「Beanの設定に何も問題がないので混乱しやすい」と注意しています。素の`BeanFactory`を使うなら`addBeanPostProcessor()`で必要なプロセッサを自分で登録する必要があり、その手間を負う理由がない限り`ApplicationContext`を選びます。

## Beanのスコープ：singletonなど6種と既定値

Beanは、IoCコンテナが生成し管理するオブジェクトです。クラスとしては普通のJavaオブジェクトと同じで、「コンテナの管理下にある」ことだけが違います。スコープは、そのBeanをどの範囲で使い回すかを決める設定で、[公式リファレンス](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html)によればSpringが標準で用意するのは6種、うち4種はWeb対応の`ApplicationContext`でしか使えません。

### 6種のスコープと使える環境

| スコープ          | インスタンスの単位         | 使える環境 |
| ------------- | ----------------- | ----- |
| singleton（既定） | コンテナ内のBean定義ごとに1個 | すべて   |
| prototype     | 要求のたびに新規          | すべて   |
| request       | HTTPリクエストごと       | Web   |
| session       | HTTPセッションごと       | Web   |
| application   | ServletContextごと  | Web   |
| websocket     | WebSocketセッションごと  | Web   |

古い解説に出てくるglobalSessionスコープはPortlet向けで、Portletサポートとともに削除されており、Spring Framework 7.0には存在しません。singletonは「コンテナ内のBean定義ごとに1個」です。同じクラスでも`@Bean`メソッドを2つ書けば同じコンテナに2個でき、同じ定義を持つ`ApplicationContext`を2つ起動すれば別々に2個できます。

### singletonへのprototype注入：インスタンスの固定と都度取得

スコープの設定では、寿命の長いBeanが寿命の短いBeanを直接保持する組み合わせに注意が必要です。singletonの`Holder`がprototypeの`Proto`をコンストラクタで受け取る構成を実行しました。

```
@Bean @Scope("prototype")
public Proto proto() { return new Proto(); }

@Bean
public Holder holder(Proto proto, ObjectProvider<Proto> provider) {
    return new Holder(proto, provider);
}
```

```
prototype同士: false
Holderに注入されたProtoは固定: true
ObjectProvider経由の2回は同じインスタンスか: false
```

`getBean(Proto.class)`を2回呼ぶと別々のインスタンスが返る（`false`）一方、`Holder`のフィールドに入った`Proto`は、`Holder`が生成された1回きりで固定されます（`true`）。注入はBeanの生成時に1度しか起きないためで、Springはこれを警告しません。毎回新しいインスタンスが必要なら、`Proto`そのものではなく`ObjectProvider<Proto>`を受け取り、使う時点で`getObject()`を呼びます（実行結果の3行目は、2回の`getObject()`が別インスタンスを返したことを示しています）。

.NETの[DIのガイドライン](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/guidelines)も、singletonがscopedやtransientの依存を取り込んで事実上singletonにしてしまう問題を「Scope leakage」として挙げています。.NETで検出の仕組みがあるのはScopedのほうだけで、[サービス有効期間の解説](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/service-lifetimes)によれば、開発環境では寿命の長いサービスからScopedを解決すると既定で例外になります（Scope validation）。Transientの取り込みはSpringのprototypeと同じく検出されません。Springではprototypeの直接注入に警告が出ないので、レビューで型とスコープの組み合わせを確認し、上の実行例のようにインスタンスの同一性をテストで確かめます。

## Beanのライフサイクル：初期化と破棄のコールバックの実行順

コンテナはBeanを生成したあと、依存を注入し、初期化コールバックを呼んでから利用可能にします。初期化の書き方は3通りあり、同じBeanに全部書いたときの順序は[公式リファレンス](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html)で決まっています。3つすべてを実装したクラスで確かめました。下のコードは検証用クラスの抜粋で、依存には`spring-context` 7.0.10と`jakarta.annotation-api` 3.0.0を使い、`new AnnotationConfigApplicationContext(Config.class)`で起動して`close()`で終了しています。

```
public static class Life implements InitializingBean, DisposableBean {
    public Life() { System.out.println("1 コンストラクタ"); }
    @Autowired
    public void setDep(Dep d) { System.out.println("2 依存の注入(setter)"); }
    @PostConstruct public void post() { System.out.println("3 @PostConstruct"); }
    @Override public void afterPropertiesSet() { System.out.println("4 afterPropertiesSet"); }
    public void initMethod() { System.out.println("5 initMethod"); }
    @PreDestroy public void pre() { System.out.println("6 @PreDestroy"); }
    @Override public void destroy() { System.out.println("7 destroy"); }
    public void destroyMethod() { System.out.println("8 destroyMethod"); }
}

// 設定クラス側
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public Life life() { return new Life(); }
```

`AnnotationConfigApplicationContext`で起動し、`close()`するまでの出力です。

```
1 コンストラクタ
2 依存の注入(setter)
3 @PostConstruct
4 afterPropertiesSet
5 initMethod
-- refresh完了
（中略）
6 @PreDestroy
7 destroy
8 destroyMethod
-- close完了
```

初期化は`@PostConstruct`→`afterPropertiesSet()`→init-method、破棄も`@PreDestroy`→`destroy()`→destroy-methodと同じ並びです。1〜5がすべて「refresh完了」より前に出ているのは、`ApplicationContext`が遅延初期化を指定していないsingletonを起動時に生成するためで、ここで例外が出ればアプリケーションは起動に失敗します。`@Lazy`を付けたBeanは、最初に要求されたときに生成されます。

基本はSpringへの依存を増やさない`@PostConstruct`・`@PreDestroy`（`jakarta.annotation`パッケージ）です。`InitializingBean`はクラスをSpringに縛るのでフレームワーク側の部品以外では避け、init-method・destroy-methodはソースを変えられない外部クラスを`@Bean`で登録するときに使います。

prototypeスコープには注意が1つあります。公式リファレンスのとおり、コンテナはprototypeを生成して渡したあと追跡しないので、初期化コールバックは呼ばれても破棄コールバックは呼ばれません。ファイルや接続を握るオブジェクトをprototypeにするなら、解放は呼び出し側の責任です。

## Spring Bootでの@Beanとコンポーネントスキャン

Spring Boot（2026年10月28日時点の最新GAは4.1.2、Spring Frameworkは7.0.10）では、IoCコンテナを組み立てるコードはほとんど書きません。`@SpringBootApplication`は`@SpringBootConfiguration`・`@EnableAutoConfiguration`・`@ComponentScan`をまとめたもので、このクラスのパッケージ以下がスキャンされ、`@Component`・`@Service`・`@Repository`・`@Controller`を付けたクラスがBeanとして登録されます。データソースやJSON変換器は、依存ライブラリに応じて自動構成が登録します。自分でBeanを登録する方法は2つです。

- **自分で書いたクラス**：クラスに`@Service`などを付けてスキャンさせる。Bean名は原則として先頭を小文字にしたクラス名になる。
- **ソースを変えられないクラスや、生成に引数や分岐が要るクラス**：`@Configuration`クラスのメソッドに`@Bean`を付け、戻り値をBeanにする。Bean名はメソッド名になる。

```
@Configuration
public class AppConfig {
    @Bean
    public PaymentClient paymentClient() {
        return new PaymentClient("https://api.example.com");
    }
}
```

Spring Bootのアノテーションの全体像は[Spring Bootのアノテーション一覧｜仕組みと使い分け・Boot 4での変更点](/tech/details/4048/)、プロジェクトの作り方から動かすまでは[Spring Bootとは｜4.1系の依存名の変更と最小Web APIの作り方](/tech/details/17478/)にまとめています。

## Spring以外のIoCコンテナ：Laravel・.NET・NestJSとの寿命の比較

IoCコンテナはJava固有の仕組みではありません。主要なWebフレームワークの寿命の種類を並べると、考え方はほぼ共通です。

| フレームワーク            | コンテナの呼び名 | 寿命の種類                      | 既定                 | 確認した版   |
| ------------------ | -------- | -------------------------- | ------------------ | ------- |
| Spring（Java）       | IoCコンテナ  | singleton・prototype・Web用4種 | singleton          | 7.0.10  |
| Laravel（PHP）       | サービスコンテナ | bind・singleton・scoped      | 解決のたびに生成           | 13.39.0 |
| .NET               | DIコンテナ   | Transient・Scoped・Singleton | 登録時に必ず指定           | 10.0.14 |
| NestJS（TypeScript） | DIシステム   | DEFAULT・REQUEST・TRANSIENT  | DEFAULT（singleton） | 12.3.2  |

違いが出るのは既定値です。Springと[NestJS](https://docs.nestjs.com/fundamentals/injection-scopes)は共有（singleton）が既定なので、利用者ごとに分離すべき可変状態をインスタンスフィールドに保存すると、その状態が利用者間で共有されます。[Laravelのサービスコンテナ](https://laravel.com/docs/13.x/container)は、インタフェースに依存しないクラスなら登録なしで自動解決し（Zero Configuration Resolution）、使い回したいときに`singleton`や、リクエスト・ジョブ単位の`scoped`で明示します。LaravelのFacadeがコンテナとどう結び付いているかは[Facadeパターンとは？窓口クラスの実装とLaravel Facadeとの違いを解説](/tech/details/17608/)で扱っています。

## IoCコンテナを使わない方がよい場面

IoCコンテナが効くのは、依存するクラスが多く、実装の差し替えやテストでのモック化を繰り返すアプリケーションです。次の2つの場面では持ち込みません。

- **数百行で終わるバッチやCLIツール**：`main`で`new`を並べる方が短く、追いやすい。
- **他のプロジェクトに配るライブラリ**：特定のコンテナを前提にすると利用側と衝突する。依存をコンストラクタで受け取る形にしておけば、どのコンテナからでもコンテナなしでも使える。

チームにコンテナの仕組みを知らないメンバーが多い場合は、導入はしても使う機能を絞ります。前の章の「素のBeanFactoryでアノテーションが無視される」「prototypeが固定される」は、どちらも例外が出ないまま動作が変わるためです。コンストラクタ注入と、状態を持たないsingletonだけに絞る規約から始めます。

既存のJavaシステムにSpringを入れるかは、依存の数と差し替えの頻度で決めます。作り直しを含めて検討するなら、弊社の[フルスクラッチ開発](/service/system/fullscratch/)でも設計から相談を受けています。

## よくある質問

### IoCコンテナとDIコンテナは違うものですか？

いまは同じものを指します。IoCが広い原則、DIがその実現手段で、SpringもLaravelもDIでIoCを実現しています。DIという呼び名は、Martin Fowlerが2004年に「IoCは汎用すぎる」として付けたものです。

### IoCとは一言でいうと何ですか？

「フレームワークがこちらのコードを呼ぶ」ことです。オブジェクトの組み立てでは、どの実装をいつ生成して誰に渡すかを、自分のクラスではなくコンテナが決めることを指します。

### Beanとは何ですか？

Springのコンテナが生成し管理するオブジェクトです。`@Component`系のアノテーションや`@Bean`メソッドで登録されたものがBeanになり、既定ではコンテナ内のBean定義ごとに1個のインスタンスが共有されます。

### @Beanと@Componentはどう使い分けますか？

自分で書いたクラスは`@Component`系を付けてスキャンさせ、外部ライブラリのクラスや生成に分岐が要るものは`@Configuration`クラスの`@Bean`メソッドで登録します。

### ApplicationContext.getBean()を業務コードで使ってもよいですか？

避けます。中身がService Locatorになり、クラスがSpringのAPIに依存します。依存はコンストラクタで受け取り、`getBean()`は`main`やテストの起点だけで使います。

## 関連記事

- [SpringのDI（依存性注入）とは？仕組みと注入方法を動くコードでわかりやすく解説](/tech/details/2303/)
- [依存性逆転の原則（DIP）とは？意味・違反コードの直し方とDI（依存性の注入）との違い](/tech/details/2761/)
- [Spring Bootのアノテーション一覧｜仕組みと使い分け・Boot 4での変更点](/tech/details/4048/)
- [Spring Bootとは｜4.1系の依存名の変更と最小Web APIの作り方](/tech/details/17478/)
- [リフレクションとは？実行時にクラス情報を操る仕組みを言語横断で解説](/tech/details/15262/)

---

出典: [IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanのスコープとライフサイクルを動くコードで解説](<https://www.issoh.co.jp/tech/details/3210/>)（株式会社一創）
