【Spring Boot】DI(依存性注入)の基本的な考え方

Java

こんにちは、かつコーチです。

Spring Bootを学び始めると、必ずと言っていいほど「DI」という言葉に出会います。
名前が難しそうに聞こえますが、考え方自体はとてもシンプルです。
この記事では、Spring Bootの中核をなすDI(依存性注入)を、身近な例に置き換えながら丁寧に解説します。

DI(依存性注入)とは?

依存とは何か

まず「依存」という言葉から整理します。
プログラミングにおける依存とは、あるクラスが動作するために、別のクラスの機能を必要としている状態のことです。

例えば、注文を処理するOrderServiceクラスが、メール送信を行うMailSenderクラスを使うとします。
このとき「OrderServiceMailSenderに依存している」と表現します。

public class OrderService {
    private MailSender mailSender = new MailSender();

    public void placeOrder() {
        // 注文処理
        mailSender.send("注文が完了しました");
    }
}

このコードでは、OrderServiceの中でnew MailSender()として自分自身がインスタンスを生成しています。
一見自然なコードに見えますが、これには問題があります。

DI(依存性注入)とは何か

DI(依存性注入、Dependency Injection)とは、あるクラスが必要とする別のクラス(依存)を、自分で生成するのではなく、外部から「注入してもらう」設計手法のことです。

public class OrderService {
    private final MailSender mailSender;

    // コンストラクタで外部から受け取る(=注入される)
    public OrderService(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void placeOrder() {
        mailSender.send("注文が完了しました");
    }
}

OrderServiceは「MailSenderが必要」ということだけを表明し、実際にどのインスタンスを使うかはコンストラクタの呼び出し元(この場合はSpring)に委ねています。
Spring Bootでは、このオブジェクトの生成と受け渡しを、フレームワークが自動的に行ってくれます。

DIが必要な理由

なぜ自分でnewしてはいけないのか

「自分でnewする」方式の一番の問題は、クラス同士が密接に結びついてしまう(密結合)ことです。
例えば、テスト時に本物のメール送信機能を呼び出したくない場合を考えてみましょう。

new MailSender()と直接書いてしまっているコードでは、テスト用の偽物(モック)に差し替えることができません。
一方、コンストラクタで外部から受け取る形になっていれば、テストのときだけ「メールを送ったふりをするだけのクラス」を注入することができます。

Springコンテナが依存関係を管理する

Spring Bootでは、@Component(や@Service@Repositoryなど)が付いたクラスは、Springコンテナと呼ばれる仕組みによって自動的にインスタンス化され、管理されます。
この、Springコンテナが管理しているインスタンスのことをBean(ビーン)と呼びます。

@Component
public class MailSender {
    public void send(String message) {
        System.out.println("メール送信: " + message);
    }
}

@Service
public class OrderService {
    private final MailSender mailSender;

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

    public void placeOrder() {
        mailSender.send("注文が完了しました");
    }
}

アプリ起動時、Springは@Componentが付いたMailSenderをBeanとして生成します。
続いて@Serviceが付いたOrderServiceのコンストラクタを見て、「MailSender型の引数が必要だ」と判断し、先ほど生成しておいたMailSenderのBeanを自動的に渡してくれます。
これが、Spring Bootにおける「依存性注入」の実際の動きです。

つまずきやすい設定・注意点

@Componentを付け忘れてBeanとして認識されない

DIを理解し始めたばかりの頃によく起きるのが、アノテーションの付け忘れによるエラーです。

❌ Before
public class MailSender {  // @Componentを付け忘れている
    public void send(String message) { ... }
}

// 起動時エラー
No qualifying bean of type 'com.example.demo.MailSender' available

筆者も最初の頃、新しいクラスを追加するたびに@Component@Serviceを付け忘れて、このNo qualifying beanエラーに何度も遭遇しました。
Spring Bootのエラーメッセージは丁寧で、「どの型のBeanが見つからないか」まで教えてくれるので、慌てずにメッセージを読むことが解決の近道です。

✅ After
@Component
public class MailSender {
    public void send(String message) { ... }
}

新しいクラスを作ったら「これはSpringに管理してほしいクラスか?」を必ず確認し、必要であればアノテーションを付ける癖をつけましょう。

応用・一歩先の使い方

DIには、コンストラクタで受け取る「コンストラクタインジェクション」以外にも、フィールドに直接注入する「フィールドインジェクション」という書き方があります。

@Service
public class OrderService {
    @Autowired
    private MailSender mailSender; // フィールドインジェクション
}

一見シンプルですが、実務ではコンストラクタインジェクションの方が推奨されています。
その理由やコンストラクタインジェクションと@Autowiredの詳しい使い分けは、本カテゴリの後半「コンストラクタインジェクションと@Autowiredの使い分け」で詳しく解説します。

まとめ

この記事のポイント

  • 依存とは、あるクラスが別のクラスの機能を必要としている状態のこと
  • DIとは、その依存を自分でnewせず、外部から注入してもらう設計手法
  • Springコンテナが@Component等の付いたクラスをBeanとして管理し、必要な場所に自動で渡してくれる
  • アノテーションの付け忘れはNo qualifying beanエラーの典型的な原因になる

次に読むべき記事

DIの考え方を理解したら、次は実際にBeanを作るときに使う「@Component・@Service・@Repositoryの違い」を見ていきましょう。

タグ: Spring Boot, 初心者向け, フレームワーク基礎

タイトルとURLをコピーしました