🧑‍💻 Spring Framework 講座【第7回】設計の決定版!〜依存性の注入(DI)の詳解とベストプラクティス〜

docs

前回は、Spring IoCコンテナと、コンテナが管理するBeanの基本的な定義方法について学びました。

今回は、そのBean同士を結びつける仕組み、すなわち「DI(依存性の注入)」を深掘りします。DIには主に3つのやり方があり、業務システム設計の品質を高めるためには、どの方法を使うべきかを理解しておくことが重要です。

1. 依存性の注入(DI)の3つの方法

DIとは、あるクラスが利用する別のクラス(依存オブジェクト)を、Springコンテナが自動で**提供(注入)**することでした。

注入する方法には、「コンストラクタ」「セッター」「フィールド」の3種類があります。

1-1. コンストラクタインジェクション (Constructor Injection)

コンストラクタ(オブジェクトを生成する際の初期化メソッド)の引数として、必要なBeanを渡してもらう方法です。

これがSpring Bootにおける最も推奨されるDIの方法です。

Java

@Service
public class ReportService {
    
    // ① 必要な依存オブジェクト(Bean)をfinalで定義
    private final DataRepository repository;

    // ② コンストラクタの引数として注入を依頼
    // Spring Boot 2.x以降、Beanが一つであれば@Autowiredは省略可能
    public ReportService(DataRepository repository) {
        // ③ 注入されたオブジェクトをフィールドに代入
        this.repository = repository; 
    }

    public void generateReport() {
        repository.fetchData();
        System.out.println("レポートを生成しました。");
    }
}

✅ メリット(ベストプラクティスの理由)

  1. 不変性(Immutability): フィールドを final にできるため、オブジェクト生成後に依存オブジェクトが勝手に書き換えられるのを防げます。これは安全な設計の基本です。
  2. 依存関係の明確化: どのBeanが必要かが一目瞭然になり、必要な依存が揃わないとオブジェクトを生成できないため、安全性が高いです。

1-2. フィールドインジェクション (Field Injection)

フィールド(クラスの変数)に対して直接、Springコンテナに値を注入してもらう方法です。記述が最も簡単です。

Java

@Service
public class ReportService {
    
    // ① フィールドに直接 @Autowired を付けて注入を依頼
    @Autowired 
    private DataRepository repository; // finalにできない

    public void generateReport() {
        repository.fetchData();
        System.out.println("レポートを生成しました。");
    }
}

❌ デメリット

  1. 不変性の欠如: final にできないため、オブジェクト生成後に依存オブジェクトが外部から変更されるリスクがあります。
  2. DIコンテナへの依存: SpringのDIコンテナがないとオブジェクトの生成が難しくなり、ユニットテストが困難になる場合があります。
  3. 隠された依存関係: 依存しているオブジェクトがフィールドに隠れてしまうため、コードが長くなると依存関係の把握が難しくなります。

1-3. セッターインジェクション (Setter Injection)

セッターメソッド(フィールドの値を設定するメソッド)を通じて、必要なBeanを注入してもらう方法です。

Java

@Service
public class ReportService {
    
    private DataRepository repository; // finalにできない

    // セッターメソッドに @Autowired を付けて注入を依頼
    @Autowired
    public void setRepository(DataRepository repository) {
        this.repository = repository;
    }

    public void generateReport() {
        repository.fetchData();
        System.out.println("レポートを生成しました。");
    }
}

✅ メリット

  • オプションの依存関係: 依存オブジェクトが必須ではない場合(注入されなくても動作する場合)に柔軟に対応できます。

❌ デメリット

  • コンストラクタインジェクションに比べて、やはり不変性を保証できません
  • 必須の依存関係に対して使うと、セッターが呼び出されない場合にオブジェクトが不完全な状態になるリスクがあります。

2. DIのベストプラクティス:コンストラクタインジェクションの利用

業務システム開発においては、原則としてコンストラクタインジェクションを使うべきです。

もし、コンストラクタの引数が多くなりすぎる場合は、それはそのクラスが**多くの役割を持ちすぎている(責務が重すぎる)**ことを示唆しています。その場合は、DIの方法を変えるのではなく、クラスを分割することを検討しましょう。

Springでは、コンストラクタインジェクションをより簡単に記述するために Lombok というライブラリの @RequiredArgsConstructor を使うことが一般的です。

Lombokを使った簡略化

Lombokの @RequiredArgsConstructor を使うと、final フィールドを引数に持つコンストラクタを自動で生成してくれるため、DIの記述がさらに簡潔になります。

Java

// Lombokのアノテーション。finalフィールドを持つコンストラクタを自動生成する。
@RequiredArgsConstructor 
@Service
public class ReportService {
    
    // finalなフィールドに自動でDIされる
    private final DataRepository repository; 

    public void generateReport() {
        repository.fetchData();
        System.out.println("Lombokで簡略化されました。");
    }
    // コンストラクタを自分で書く必要がない!
}

3. @Autowired の場所

@Autowired は、Springコンテナに「ここに依存オブジェクトを注入してください」と指示するためのアノテーションです。

注入方法@Autowired の場所
コンストラクタコンストラクタの上(Spring Bootでは省略可能)
フィールドフィールドの上
セッターセッターメソッドの上

Spring Bootでは、特に理由がなければLombokを活用したコンストラクタインジェクションを採用するのが、最も安全でモダンな書き方とされています。

✅ 本日のまとめ

  • DIにはコンストラクタ、フィールド、セッターの3種類がある。
  • コンストラクタインジェクションは、不変性を保証し、依存関係を明確にするため、最も推奨される方法である。
  • final フィールドを使うことで、Beanの安全性が高まる。
  • 業務開発では、Lombokの @RequiredArgsConstructor を使ってコンストラクタインジェクションを簡略化するのが一般的である。

🔔 次回予告

今回でDIの仕組みを完全に理解しました。次回は、私たちが前々回から使っている @Service@Controller といったSpringのアノテーションに焦点を当てます。

これらのアノテーションが、どのようにしてSpringコンテナにBeanとして認識され、またどのように役割を分担しているのかを、基礎となる @Autowired とともに詳しく解説します。

次回:【第8回】アノテーションベースの設定 (1) – @Autowired にご期待ください!

コメント

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