---
title: "SpringのDI（依存性注入）とは？仕組みと注入方法を動くコードでわかりやすく解説"
url: "https://www.issoh.co.jp/tech/details/2303/"
published: 2024-05-28
updated: 2026-10-12
categories: ["Spring"]
publisher: "株式会社一創"
---

# SpringのDI（依存性注入）とは？仕組みと注入方法を動くコードでわかりやすく解説

SpringのDI（依存性注入）は、クラスが使う部品（依存オブジェクト）を自分で`new`せず、Springから受け取る仕組みです。言葉で読むと抽象的なので、この記事ではDIなし・ありのコードを並べ、Springのコンテナを実際に起動したときの出力を見ながら説明します。掲載コードは Spring Framework 7.0.9（Spring Boot 4.1.1 が採用している版）と JDK 25 で実行し、出力とエラー文はその結果をそのまま載せています。

## まとめ：SpringのDIで押さえる5つの要点

- **DIとは**：依存する相手を自分で`new`せず、外から受け取る設計。Springでは DIコンテナ（`ApplicationContext`）が「Bean」を生成し、必要な場所へ渡す。
- **注入方法はコンストラクタ注入が基本**：Springの公式リファレンスがコンストラクタ注入を推奨し、セッター注入は主に妥当な既定値を持つ任意の依存向けとしている。本記事では、新規コードにフィールド注入を採用しない方針とする。
- **1つだけ受け取る注入先に同じ型のBeanが2つあると起動に失敗する**：`NoUniqueBeanDefinitionException`になる。`@Primary`・`@Qualifier`・`@Fallback`（6.2以降）で選ぶ。
- **Beanは既定でシングルトン**：同じコンテナ内の同じBean定義では1つのインスタンスを共有するので、利用者ごとの状態をフィールドに持たせない。
- **循環参照はSpring Bootでは既定で起動エラー**：Spring Boot 2.6 以降の挙動。設定で許可せず、依存の向きを直す。

## SpringのDI（依存性注入）の意味：必要な部品を外から受け取る設計

DI（Dependency Injection＝依存性注入）の「依存」は「AがBを使っている（AはBがないと動かない）」関係を、「注入」はそのBを外から差し込むことを指します。料理に例えると、シェフ（クラス）が食材（依存オブジェクト）を自分で畑に採りに行くのがDIなし、仕入れ担当（Springのコンテナ）が食材を届けてくれるのがDIありです。シェフは「何を使うか」だけを宣言し、「どこからどう調達するか」は気にしません。

### DIなしのコードとDIありのコードの違い

DIを使わない場合、`UserService`は依存先の`UserRepository`を自分で生成します。

```
public class UserService {
    private final UserRepository userRepository = new JdbcUserRepository();

    public void register(String name) {
        userRepository.save(name);
    }
}
```

この書き方では`UserService`が`JdbcUserRepository`という具体的な実装に固定されます。テストでDBを使わない偽物に差し替えたくても、内部で生成しているので外から置き換えられません。

DIを使う場合は、依存先をコンストラクタの引数として受け取ります。

```
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void register(String name) {
        userRepository.save(name);
    }
}
```

変わったのは「作る」から「受け取る」への一点だけです。`UserService`はインターフェース`UserRepository`にしか依存しなくなり、本番ではDB実装を、テストでは偽物を渡せます。この「渡す」役をSpringが自動で担うのが、SpringのDIです。

### DIとIoC（制御の反転）・DIコンテナの関係

DIはIoC（Inversion of Control＝制御の反転）とセットで語られます。オブジェクトの生成と組み立てを自分のコードからフレームワークへ移すのがIoCで、DIはその代表的な実現方法です。DIという呼び名は、Martin Fowler が2004年1月に公開した[Inversion of Control Containers and the Dependency Injection pattern](https://martinfowler.com/articles/injection.html)で広まりました。同記事は「Separating Configuration from Use」という節を設け、サービスの設定（configuration）を利用（use）から分離することが要点だと述べています。どの実装を使うかという「設定」を、それを使うクラスの外に出す。DIの価値はこの一点に集約されます。

IoCコンテナの内部（BeanFactoryとApplicationContextの違い、Beanのスコープとライフサイクル）は[IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanをわかりやすく解説](/tech/details/3210/)で掘り下げています。DIと名前の似た設計原則DIP（依存性逆転の原則）との違いは[依存性の注入（DI）と依存性逆転の原則（DIP）の違い｜コード例で使い分け](/tech/details/2761/)を参照してください。

## DIコンテナを動かして仕組みを確かめる最小構成

SpringのDIは Spring Boot がなくても動きます。`spring-context`（Spring Boot のスターターにも含まれるコアの部品）だけで、コンテナの動きを確認できます。Spring Boot で新しくプロジェクトを作る手順は[Spring Bootとは｜4.1系の依存名の変更と最小Web APIの作り方](/tech/details/17478/)にまとめています。

### @Configuration・@BeanによるBean定義とコンテナ起動

Mavenなら`pom.xml`の`dependencies`に次の1つを足すだけで準備は終わりです。

```
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.9</version>
</dependency>
```

次のコードは、`@Configuration`を付けた設定クラスに「どの実装を使うか」を書き、`AnnotationConfigApplicationContext`でコンテナを起動します。`UserService`自身は`InMemoryUserRepository`の存在を知りません。

```
package demo;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

interface UserRepository {
    void save(String name);
}

class InMemoryUserRepository implements UserRepository {
    InMemoryUserRepository() {
        System.out.println("1. InMemoryUserRepository を生成");
    }
    public void save(String name) {
        System.out.println("保存: " + name);
    }
}

class UserService {
    private final UserRepository userRepository;

    UserService(UserRepository userRepository) {
        System.out.println("2. UserService を生成（依存を受け取る）");
        this.userRepository = userRepository;
    }
    void register(String name) {
        userRepository.save(name);
    }
}

@Configuration
class AppConfig {
    @Bean
    UserRepository userRepository() {
        return new InMemoryUserRepository();
    }

    @Bean
    UserService userService(UserRepository userRepository) {
        return new UserService(userRepository);
    }
}

public class App {
    public static void main(String[] args) {
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            System.out.println("3. コンテナの起動完了");
            UserService first = context.getBean(UserService.class);
            UserService second = context.getBean(UserService.class);
            first.register("tanaka");
            System.out.println("同じインスタンスか: " + (first == second));
        }
    }
}
```

`src/main/java/demo/App.java`に保存し、`mvn compile exec:java -Dexec.mainClass=demo.App`で実行した結果は次のとおりです。

```
1. InMemoryUserRepository を生成
2. UserService を生成（依存を受け取る）
3. コンテナの起動完了
保存: tanaka
同じインスタンスか: true
```

### DIコンテナ起動時の依存解決・Bean生成・共有

この5行に、DIコンテナの仕事がそのまま表れています。

- **依存される側から先に生成する**：`userService`メソッドの引数が`UserRepository`なので、コンテナは先に`InMemoryUserRepository`を作り（1行目）、それを渡して`UserService`を作ります（2行目）。順番をコードに書いていないのに、引数の型から組み立て順が決まります。
- **起動の時点で全部作り終える**：「3\. コンテナの起動完了」より前に2つのBeanが生成されています。`ApplicationContext`は既定で、シングルトンのBeanを起動時にまとめて生成します。設定の誤りが、最初のリクエストではなく起動時に見つかるのはこのためです。
- **何度取り出しても同じインスタンス**：`getBean`を2回呼んでも`true`です。Springの公式リファレンスは[Bean Scopes](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html)で「The singleton scope is the default scope in Spring.」と明記しています。

### Beanを登録する4つの方法

上の例は`@Bean`メソッドで登録しましたが、実務ではクラスに`@Component`系のアノテーションを付けて自動検出させる方法が中心です。Spring Framework 7.0 では、条件に応じてコードでBeanを登録する`BeanRegistrar`が加わりました（公式リファレンス[Programmatic Bean Registration](https://docs.spring.io/spring-framework/reference/core/beans/java/programmatic-bean-registration.html)）。

| 登録方法          | 書き方                           | 向いている対象              |
| ------------- | ----------------------------- | -------------------- |
| コンポーネントスキャン   | クラスに`@Component`・`@Service`など | 自作クラスの大半             |
| Java Config   | `@Configuration`内の`@Bean`メソッド | 外部ライブラリのクラス          |
| BeanRegistrar | `BeanRegistrar`を実装し`@Import`  | 環境で登録内容を変えるとき（7.0以降） |
| XML設定         | XMLファイルのbean定義                | 既存システムの保守            |

新規開発では、自作クラスはコンポーネントスキャン、手を入れられない外部クラスは`@Bean`メソッドで登録すれば足ります。`BeanRegistrar`はライブラリやフレームワークを作る側の道具で、アプリケーションのコードで最初に覚える必要はありません。

## Springの3つの注入方法と使い分け

Beanをどの経路で受け取るかには、コンストラクタ注入・セッター注入・フィールド注入の3つがあります。選び方の結論は公式リファレンスの[Dependency Injection](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)の章にあり、迷う余地はほとんどありません。

### コンストラクタ注入：必須依存の明示と初期化

公式リファレンスは「The Spring team generally advocates constructor injection」と書き、理由として、部品を不変（immutable）にでき、必須の依存が`null`にならず、完全に初期化された状態で渡されることを挙げています。

```
@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
```

コンストラクタが1つだけなら`@Autowired`は書かなくても注入されます。これは Spring Framework 4.3（2016年）で入った挙動で、当時の[Springブログの告知](https://spring.io/blog/2016/03/04/core-container-refinements-in-spring-framework-4-3)で紹介されています。コンストラクタが複数あるときは、どれを使うかを`@Autowired`で指定します。

引数が5個、6個と増えてきたら、注入方法ではなくクラスの責務を疑ってください。公式リファレンスも、コンストラクタ引数が多いことを「クラスが責務を持ちすぎている」兆候としています。

### セッター注入：任意依存を中心とした使い分け

公式リファレンスは、セッター注入を「クラス内で妥当な既定値を割り当てられる任意の依存」に主に使うべきだとしています。セッターに付けた`@Autowired`も既定では必須扱いで、依存が見つからなければBeanの生成に失敗します。任意の依存を`null`のまま許す場合は、利用時のチェックが必要です。

```
@Service
public class ReportService {
    private MailSender mailSender; // 無ければメール送信を省く

    @Autowired(required = false)
    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }
}
```

### フィールド注入を新規コードで避ける3つの理由

フィールドに直接`@Autowired`を付ける書き方は短く書けますが、新規コードでは使いません。

```
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}
```

- **finalにできない**：生成後に書き込まれるため、不変にできません。
- **依存をコンストラクタから渡してテストできない**：外から依存を渡す口がないので、単体テストでもコンテナかリフレクションが必要になります。
- **循環参照が素のSpringでは通ってしまう**：後述のとおり、素の Spring Framework ではフィールド注入どうしの循環参照が黙って成立します。コンストラクタ注入なら起動時に必ず失敗します。

既存コードがフィールド注入なら、そのクラスを修正するついでにコンストラクタ注入へ移します。

## 同じ型のBeanが複数あるときの選び方：@Primary・@Qualifier・@Fallback

DIで最初につまずきやすいのが「同じインターフェースの実装が2つある」場面です。次のように通知手段を2つ登録し、`OrderService`に`Notifier`を1つ渡そうとすると、コンテナはどちらを渡すか決められません。

```
interface Notifier {
    String channel();
}

record OrderService(Notifier notifier) {
}

@Configuration
class NotifierConfig {
    @Bean
    Notifier mailNotifier() {
        return () -> "mail";
    }

    @Bean
    Notifier slackNotifier() {
        return () -> "slack";
    }

    @Bean
    OrderService orderService(Notifier notifier) {
        return new OrderService(notifier);
    }
}
```

起動はUnsatisfiedDependencyExceptionで失敗し、その原因として次のNoUniqueBeanDefinitionExceptionが記録されます。

```
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'demo.Notifier' available: expected single matching bean but found 2: mailNotifier,slackNotifier
```

メッセージ末尾の「found 2: mailNotifier,slackNotifier」が候補のBean名です。解決方法は3つあり、同じ構成で付け替えて実行した結果は次のとおりでした。

| 付けたもの                                    | 付ける場所                       | 渡されたBean   |
| ---------------------------------------- | --------------------------- | ---------- |
| なし                                       | –                           | 起動失敗（上の例外） |
| `@Primary`                               | mailNotifier の定義            | mail       |
| `@Primary`＋`@Qualifier("slackNotifier")` | Primaryは定義、Qualifierは受け取る引数 | slack      |
| `@Fallback`                              | slackNotifier の定義           | mail       |

使い分けは次のとおりです。

- **`@Primary`**：「普段はこれ」と決まっている既定の実装に付けます。
- **`@Qualifier`**：受け取る側が個別に指定します。上の3行目では、`@Qualifier`が候補をslackNotifierに絞るため、mailNotifierの`@Primary`は選択に影響しません。
- **`@Fallback`**：Spring Framework 6.2 で追加された、`@Primary`の逆です。「ほかに候補が無いときだけ使う」実装に付け、残りが1つならそれが選ばれます（公式リファレンス[Fine-tuning Annotation-based Autowiring with @Primary or @Fallback](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired-primary.html)）。

どれを選ぶかの判断基準は「誰が知っているべきか」です。既定の実装を設定側で決めたいなら`@Primary`、特定のクラスだけ別の実装を使うなら`@Qualifier`にします。全実装を受け取りたいときは`List<Notifier>`で受け取れば、登録された全Beanが渡されます。

## DIで単体テストが書きやすくなる理由

DIのメリットとして「テストしやすい」とよく書かれますが、具体的には**Springを起動せずに、テスト用の偽物を`new`で渡せる**ことです。ここでは、名前が空白なら保存せずに例外を投げる`UserService`をテストします。

```
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void register(String name) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("name is blank");
        }
        userRepository.save(name);
    }
}
```

コンストラクタ注入で書いてあるので、JUnit だけで次のように検証できます。

```
package demo;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {

    // テスト用の偽物。DBにはつながない
    static class FakeUserRepository implements UserRepository {
        final List<String> saved = new ArrayList<>();
        public void save(String name) { saved.add(name); }
    }

    @Test
    void registerSavesUser() {
        var repository = new FakeUserRepository();
        var service = new UserService(repository); // Springを起動せず new で渡す

        service.register("tanaka");

        assertEquals(List.of("tanaka"), repository.saved);
    }

    @Test
    void blankNameIsRejected() {
        var repository = new FakeUserRepository();
        var service = new UserService(repository);

        assertThrows(IllegalArgumentException.class, () -> service.register(" "));
        assertTrue(repository.saved.isEmpty());
    }
}
```

テストは`src/test/java/demo`に置きます。Spring Boot 4.1.1 のテスト用スターター（JUnit 6.0.3 を含む）を依存に入れて`mvn test`で実行すると、2件とも成功し、所要時間は2件合わせて0.1秒でした。コンテナを起動しないので速く終わります。

偽物のクラスを書く代わりに、Spring Boot のテスト用スターターに含まれる Mockito で`mock(UserRepository.class)`を渡す方法もあります。偽物とモックの使い分けは[モックとは？スタブとの違い・テストダブル5分類と5言語の書き方を実行例で解説](/tech/details/13505/)、JUnitの基本は[JUnitとは？Javaの単体テストフレームワークの基本と書き方【2026年版】](/tech/details/3064/)を参照してください。

## SpringのDIでつまずきやすい2つの落とし穴

### シングルトンBeanにおける利用者別の状態共有リスク

Beanは既定でシングルトンなので、フィールドに値を持たせると全利用者で共有されます。

```
@Component
class VisitCounter {
    private int count = 0; // シングルトンなので全利用者で共有される

    int visit() {
        return ++count;
    }
}
```

利用者Aと利用者Bがそれぞれ1回ずつ`visit()`を呼ぶと、出力は次のようになります。

```
利用者Aの1回目: 1
利用者Bの1回目: 2
```

Bにとっては1回目なのに2が返ります。Webアプリでは複数のリクエストが同じBeanを同時に使うため、ログインユーザー名や処理中の値をフィールドに入れると、別の利用者の値が混ざります。公式リファレンスも「状態を持つBeanにはprototypeスコープ、状態を持たないBeanにはsingletonスコープを使う」としています。サービスクラスは状態を持たない作りにし、値はメソッドの引数と戻り値で受け渡します。サービス層の書き方は[サービスクラスとは？Java・Springでの役割と書き方、コントローラーとの違い](/tech/details/3015/)で解説しています。

### 循環参照はSpring Bootでは既定で起動エラー

AがBを、BがAを必要とする循環参照は、注入方法と、Spring Boot を使うかどうかで結果が変わります。同じ2クラスで試した結果は次のとおりです。

| 構成           | 素のSpring Framework 7.0.9 | Spring Boot 4.1.1 |
| ------------ | ------------------------ | ----------------- |
| コンストラクタ注入どうし | 起動失敗                     | 起動失敗              |
| フィールド注入どうし   | 起動成功（循環が残る）              | 起動失敗              |

コンストラクタ注入では、AとBのどちらも相手より先に作れないため、どの構成でも`BeanCurrentlyInCreationException`になります。フィールド注入では素の Spring Framework がいったん作りかけのBeanを渡して循環を成立させてしまいますが、Spring Boot は2.6から循環参照を既定で禁止しました（[Spring Boot 2.6 Release Notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes)）。Spring Boot 4.1.1 では次の表示で起動が止まります。

```
The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  bootCircular.X (field demo.BootCircular$Y demo.BootCircular$X.y)
↑     ↓
|  bootCircular.Y (field demo.BootCircular$X demo.BootCircular$Y.x)
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
```

メッセージにあるとおり、`spring.main.allow-circular-references=true`は「最後の手段」です。設定で黙らせず、両方が使っている処理を第3のクラスへ切り出して、依存の向きを一方向にそろえます。

## DIで使う主要アノテーションの早見表

DIまわりで最初に覚えるアノテーションは次の範囲です。

| アノテーション                             | 付ける場所     | 役割                    |
| ----------------------------------- | --------- | --------------------- |
| `@Component`                        | クラス       | 自動検出の対象にする汎用の印        |
| `@Service`                          | クラス       | 業務ロジック層のBean          |
| `@Repository`                       | クラス       | データアクセス層。設定により例外変換の対象 |
| `@Controller`                       | クラス       | Web層のBean             |
| `@Configuration`・`@Bean`            | クラス・メソッド  | メソッドの戻り値をBeanにする      |
| `@Autowired`                        | コンストラクタなど | 注入の指示（コンストラクタ1つなら省略可） |
| `@Primary`・`@Qualifier`・`@Fallback` | 定義・引数     | 同じ型の候補から選ぶ            |

`@Service`・`@Repository`・`@Controller`はどれも`@Component`の一種で、Beanとして登録される点は同じです。層を示すことで設計の意図が読み手に伝わり、`@Repository`はデータアクセス例外の変換対象にもなります。各アノテーションの細かな挙動は[Springアノテーションとは？仕組みと主要アノテーションの使い分けを解説](/tech/details/2639/)、Spring Boot 全体の一覧は[Spring Bootのアノテーション一覧｜役割別の早見表とBoot 4での変更点](/tech/details/4048/)にあります。

## Spring以外のDIとの違い：Jakarta CDI・Guice・Dagger

DIはSpring固有の考え方ではありません。Javaでは、Jakarta EE の標準仕様である Jakarta CDI（Contexts and Dependency Injection）、Google の軽量ライブラリ Guice、コンパイル時に注入コードを生成する Dagger（Androidで広く使われる）があります。Java以外でも、Angular や NestJS はフレームワーク自体にDIを組み込んでいます。

これらと比べたSpringの特徴は、DIコンテナを土台に、Web・データアクセス・セキュリティ・バッチまでを同じBeanの仕組みで揃えている点です。Spring Framework の全体像とSpring Bootとの関係は[Spring Frameworkとは？特徴・DI/AOPとSpring Bootとの違い・最新版7を解説【2026年最新】](/tech/details/3867/)で整理しています。業務システムを Java と Spring で新規に作る場合の進め方は、[フルスクラッチ開発](/service/system/fullscratch/)の支援内容も参考にしてください。

## よくある質問

### DIとDIコンテナは何が違いますか？

DIは「依存を外から渡す」という設計の考え方で、DIコンテナはそれを実際に行うSpringの部品（`ApplicationContext`）です。DIはコンテナがなくても、コンストラクタに手で`new`したものを渡せば成り立ちます。コンテナは、その組み立てを依存関係の数が増えても自動でこなす役割です。

### SpringでDIを使うメリットは何ですか？

主なメリットは2つです。実装を差し替えるときに使う側のクラスを修正しなくてよいこと、そしてSpringを起動せずに偽物の依存を渡して単体テストできることです。加えて、既定の非遅延シングルトンBeanは起動時に生成・組み立てされるため、依存関係の設定誤りを起動時に検出できます。遅延初期化や他のスコープでは、生成時まで検出されない場合があります。

### @Autowiredは必ず書く必要がありますか？

コンストラクタが1つだけのクラスなら不要です（Spring Framework 4.3以降）。複数のコンストラクタから注入先を明示する場合は`@Autowired`を使います。どれにも付けていなくても、引数なしのコンストラクタがあればそれが使われます。セッターやフィールドには、`@Autowired`のほか`@Inject`や`@Resource`も利用できます。

### @Componentと@Serviceはどう違いますか？

どちらもクラスをBeanとして登録します。`@Service`は`@Component`の一種で、業務ロジック層であることを示す意味付けが違いです。層が決まっているクラスには`@Service`・`@Repository`・`@Controller`を使い、どの層にも当てはまらない部品に`@Component`を使います。

### Spring BootとSpring FrameworkでDIの書き方は変わりますか？

アノテーションと注入方法は同じです。Spring Boot は`@SpringBootApplication`を付けたクラスのパッケージ以下を自動でスキャンし、ライブラリのBeanも自動構成で登録します。挙動の差として、Spring Boot は循環参照を既定で禁止している点に注意してください。

## 関連記事

- [IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanをわかりやすく解説](/tech/details/3210/)
- [依存性の注入（DI）と依存性逆転の原則（DIP）の違い｜コード例で使い分け](/tech/details/2761/)
- [Spring Frameworkとは？特徴・DI/AOPとSpring Bootとの違い・最新版7を解説【2026年最新】](/tech/details/3867/)
- [Spring Bootのアノテーション一覧｜役割別の早見表とBoot 4での変更点](/tech/details/4048/)
- [モックとは？スタブとの違い・テストダブル5分類と5言語の書き方を実行例で解説](/tech/details/13505/)

---

出典: [SpringのDI（依存性注入）とは？仕組みと注入方法を動くコードでわかりやすく解説](<https://www.issoh.co.jp/tech/details/2303/>)（株式会社一創）
