Spring Frameworkにおける依存性の注入(DI)の基本と実装パターン

Spring Frameworkの核となる機能の一つが「依存性の注入(Dependency Injection: DI)」です。これは、オブジェクト間の依存関係をソースコードに直接記述するのではなく、外部(Springコンテナ)から注入することで、モジュール間の結合度を下げ、テストの容易性や保守性を向上させる設計パターンです。

1. オブジェクト生成の進化と依存性の課題

DIの必要性を理解するために、Springを使用しない従来の手法がどのように進化し、どのような課題を抱えていたかを振り返ります。

直接インスタンス化(密結合)

最も単純な方法は、クラス内部で依存するオブジェクトを直接 new することです。

public class ProductService {
    // ServiceがRepositoryの実装を直接知っている状態
    private ProductRepository repository = new ProductRepositoryImpl();
}

この手法では、ProductRepository の実装を切り替える際に ProductService 自体を修正して再コンパイルする必要があり、柔軟性が欠如します。

ファクトリパターンの利用

依存関係を抽象化するために、ファクトリクラスを導入して文字列ベースで依存先を取得する手法が使われていました。

public class ComponentFactory {
    public static Object getBean(String className) {
        try {
            return Class.forName(className).getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create bean", e);
        }
    }
}

// Service側での利用
public class ProductService {
    private ProductRepository repository = (ProductRepository) ComponentFactory.getBean("com.example.dao.ProductRepositoryImpl");
}

これにより、new を直接記述することはなくなりましたが、依然としてファクトリへの依存が残り、実行時まで型チェックが難しいという課題がありました。

2. SpringにおけるDIの実装方法

Springでは、IoC(制御の逆転)コンテナがオブジェクト(Bean)のライフサイクルを管理し、定義に基づいて依存関係を注入します。主な注入方法は以下の通りです。

コンストラクタ注入(Constructor Injection)

オブジェクト生成時に必要な依存先をすべて渡す方法です。フィールドを final にできるため、不変性を確保でき、推奨される手法です。

<!-- XML設定 -->
<bean id="dataRepository" class="com.example.DataRepository" />
<bean id="businessService" class="com.example.BusinessService">
    <constructor-arg ref="dataRepository" />
</bean>
// Javaクラス
public class BusinessService {
    private final DataRepository repository;

    public BusinessService(DataRepository repository) {
        this.repository = repository;
    }
}

セッター注入(Setter Injection)

setterメソッドを通じて依存関係を注入します。オプションの依存関係がある場合に適しています。

<bean id="notifyService" class="com.example.NotifyService">
    <property name="mailClient" ref="mailClient" />
</bean>

アノテーションによる自動配線(Autowiring)

@Autowired アノテーションを使用すると、Springが型を基準に適切なBeanを探して自動的に注入します。

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    public void processOrder() {
        orderRepository.save();
    }
}

@Autowired はフィールド、コンストラクタ、またはsetterメソッドに付与可能です。

3. JavaConfigによる依存定義

XML設定の代わりに、Javaクラスを使用してBeanの依存関係を明示的に定義することも一般的です。これには @Configuration と @Bean を使用します。

@Configuration
public class AppConfig {

    @Bean
    public InventoryRepository inventoryRepository() {
        return new InventoryRepositoryImpl();
    }

    @Bean
    public ShippingService shippingService() {
        // メソッド呼び出しによる注入
        return new ShippingService(inventoryRepository());
    }

    @Bean
    public PaymentService paymentService(InventoryRepository repository) {
        // 引数による注入(Springが自動的にBeanを渡す)
        PaymentService service = new PaymentService();
        service.setRepository(repository);
        return service;
    }
}

JavaConfigを使用すると、IDEによるコード補完やコンパイル時のチェックが可能になり、設定ミスを大幅に減らすことができます。

4. 自動配線のモード

XMLベースの構成では、autowire 属性を指定することで自動配線の挙動を制御できます。

  • byName: プロパティ名と同じIDを持つBeanを注入します。
  • byType: 同じ型のBeanをコンテナ内から探し出し注入します。同じ型のBeanが複数存在するとエラーになります。
  • constructor: コンストラクタの引数の型に一致するBeanを注入します。

現代の開発では、明示的なJavaConfigまたは @Component スキャンと @Autowired の組み合わせが主流となっています。

タグ: SpringFramework DependencyInjection Java SpringCore IoC

9月20日 08:50 投稿