この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:guest
更新日時:2026-06-11 07:12:00

タイトル: DI
SEOタイトル: DI(依存性注入)完全ガイド(Dependency Injection / IoC / Spring / CDI / コンストラクタ・セッター注入)

この記事の要点
  • DI(Dependency Injection / 依存性注入)は、オブジェクトが必要とする依存物を外部から渡す設計パターン
  • 結合度を下げ、テスト可能性と差し替え可能性を高めるのが目的。テスト時はモック実装に差し替え可能
  • 注入方法はコンストラクタ注入(推奨)、セッター注入フィールド注入の 3 種類
  • Spring Framework / CDI(Jakarta EE) / Google Guice 等の DI コンテナが代表的
  • DI とIoC(制御の反転)はセット概念。「依存先を new するのは呼び出される側」が IoC

DI(依存性注入)とは

DI(Dependency Injection)は、あるオブジェクトが他のオブジェクト(依存物)を必要とするとき、自分で生成せず外部から渡してもらう設計パターンです。「依存性注入」と訳されます。

DI を使うと、コンポーネント同士の結合度が下がり、ユニットテストでモックに差し替えたり、実装を入れ替えたりが容易になります。Java の Spring / CDI、.NET の Microsoft.Extensions.DependencyInjection、Angular / NestJS など、現代のフレームワークの多くが DI を中核機能として備えています。

DI なしのコード

public class OrderService {
    private final MailSender mailSender;

    public OrderService() {
        // 自分で実装を選んで new
        this.mailSender = new SmtpMailSender("smtp.example.com");
    }

    public void placeOrder(Order order) {
        // ...
        mailSender.send(order.getEmail(), "注文を受け付けました");
    }
}

問題点:

  • テストでモックに差し替えられない — テストでも本物の SMTP に送信してしまう
  • 別の MailSender(SES、SendGrid 等)に変えるのが困難
  • 結合度が高く、SmtpMailSender が無いとそもそもコンパイルできない

DI ありのコード(コンストラクタ注入)

public class OrderService {
    private final MailSender mailSender;

    // 依存物は外から受け取る
    public OrderService(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void placeOrder(Order order) {
        // ...
        mailSender.send(order.getEmail(), "注文を受け付けました");
    }
}

// 呼び出し側
MailSender sender = new SmtpMailSender("smtp.example.com");
OrderService service = new OrderService(sender);

// テスト時はモックを渡せる
OrderService service = new OrderService(new MockMailSender());

3 種類の注入方法

1. コンストラクタ注入(推奨)

public class OrderService {
    private final MailSender mailSender;

    public OrderService(MailSender mailSender) {
        this.mailSender = mailSender;
    }
}

メリット: 不変(final)で安全必須依存が明確、テストしやすい。

2. セッター注入

public class OrderService {
    private MailSender mailSender;

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }
}

メリット: オプショナルな依存を表現しやすい。デメリット: 注入忘れの NPE リスク。

3. フィールド注入(非推奨)

public class OrderService {
    @Autowired
    private MailSender mailSender;  // フレームワーク依存
}

デメリット: フレームワーク無しでは動かない、final にできない、テストしにくい。Spring も現在はコンストラクタ注入を推奨

Spring Framework での DI

@Service
public class OrderService {
    private final MailSender mailSender;

    public OrderService(MailSender mailSender) {  // 自動的に注入
        this.mailSender = mailSender;
    }
}

@Component
public class SmtpMailSender implements MailSender {
    @Override
    public void send(String to, String body) { /* ... */ }
}

Spring が起動時に @Service@Component を持つクラスをスキャンし、コンストラクタの引数型に合うインスタンスを自動で渡してくれます。

CDI(Jakarta EE)での DI

@ApplicationScoped
public class OrderService {

    @Inject
    private MailSender mailSender;  // フィールド注入

    public void placeOrder(Order order) {
        mailSender.send(order.getEmail(), "受付完了");
    }
}

DI と IoC の関係

用語意味
IoC(Inversion of Control)制御の反転。「呼ぶ側ではなく呼ばれる側が制御する」
DI(Dependency Injection)IoC の具体的な実現方法のひとつ
DI コンテナ依存解決を自動化するフレームワーク(Spring 等)

メリット

  • テスト可能性: モック / スタブに容易に差し替え可能
  • 差し替え容易性: 実装変更が呼び出し側に波及しない
  • SoC(関心の分離): 「使う」と「作る」が分離される
  • DI コンテナでライフサイクル(シングルトン / リクエストスコープ等)を一元管理

デメリット / 注意点

  • 学習コスト: フレームワーク特有の概念が多い
  • スタックトレースが追いづらい: コンテナ層で抽象化される
  • 過剰な抽象化: 小さなアプリで DI コンテナを使うとオーバーキル

DI が活躍する典型シーン

シーン
外部 API クライアント本番 API / テスト用モック
データベース層本物 DB / インメモリ DB
メール送信SMTP / SES / ログ出力のみ
時刻取得システム時刻 / 固定時刻(テスト用)
ロガー標準出力 / ファイル / リモートログ

FAQ

Q: シングルトンパターンで十分では?
A: シングルトンはグローバル状態を生み、テストでモックに差し替えられません。DI のシングルトンスコープ(コンテナ管理)の方が、差し替え可能で安全です。

Q: DI コンテナを使わず手で new するのと違う?
A: 概念上の DI は手で new でも実現可能(Poor Man's DI)。コンテナの価値は大規模なオブジェクトグラフ管理とライフサイクル制御です。

Q: Spring と Guice、どちらを使うべき?
A: Web アプリ全般なら Spring(実質デファクト)、軽量に使いたいなら Guice。Java EE 環境なら CDI が標準。

関連

  • IoC — 制御の反転
  • Spring Framework — Java の代表的 DI コンテナ
  • CDI — Jakarta EE 標準の DI 仕様
  • Google Guice — Google 発の軽量 DI
  • SOLID の D — 依存性逆転の原則(DIP)