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 の組み合わせが主流となっています。