Spring FrameworkにおけるBeanの依存性注入と設定手法

Spring FrameworkのDI(Dependency Injection)コンテナにおいて、Beanの定義は「何を作るか」を決めるものですが、注入(Injection)は「どのように初期化し、依存関係を解決するか」を決めるプロセスです。主な設定方法として、XMLファイルを用いる古典的なアプローチと、アノテーションを活用するモダンなアプローチの2つが存在します。本記事では、これらの手法について技術的に解説します。

XML設定によるBeanの注入

XMLベースのDIには、大きく分けて「セッター注入」、「コンストラクタ注入」、「ファクトリメソッド注入」の3種類があります。

1. セッター注入(Setter Injection)

これは最も基本的な手法であり、引数なしのデフォルトコンストラクタでインスタンスを生成した後、Setterメソッドを経由してプロパティ値や他のBeanを設定します。柔軟性が高く、必須でない依存関係の設定に適しています。

以下に、データソース(IDataSource)に依存するサービスクラス(DataService)の例を示します。

public class DataService {
    private IDataSource source;
    private String serviceName;

    // Setterメソッド
    public void setSource(IDataSource source) {
        this.source = source;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public void execute() {
        System.out.println("Service: " + serviceName + ", Source: " + source.getConnectionInfo());
    }
}

対応するSpringのXML設定は以下の通りです。<property>タグを使用して値を設定します。value属性はプリミティブ型や文字列、ref属性は他のBeanの参照に使用されます。

<bean id="dbSource" class="com.example.DbDataSource" />

<bean id="dataService" class="com.example.DataService">
    <property name="source" ref="dbSource" />
    <property name="serviceName" value="MainDataProcessor" />
</bean>

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

コンストラクタ注入では、オブジェクトの生成時に必須となる依存関係をコンストラクタの引数として渡します。これにより、不変(Immutable)なオブジェクトを作成しやすくなります。XMLでは<constructor-arg>タグを使用します。

public class DataService {
    private IDataSource source;
    private String serviceName;

    public DataService(String serviceName, IDataSource source) {
        this.serviceName = serviceName;
        this.source = source;
    }
    // ...
}

index属性で引数の位置を指定するか、型に基づいて自動的にマッピングされます。

<bean id="dataService" class="com.example.DataService">
    <constructor-arg index="0" value="MainDataProcessor" />
    <constructor-arg index="1" ref="dbSource" />
</bean>

3. ファクトリメソッド注入(Factory Method Injection)

ファクトリメソッドパターンを利用して、インスタンスの生成をファクトリクラスに委譲する方法です。静的ファクトリメソッドとインスタンスファクトリメソッドの2通りがあります。

静的ファクトリメソッド:

クラスのインスタンス化を行わず、クラス名経由でメソッドを呼び出します。

public class ServiceFactory {
    public static DataService createStaticService() {
        return new DataService("StaticService", new DbDataSource());
    }
}
<bean id="staticService" class="com.example.ServiceFactory" factory-method="createStaticService" />

インスタンスファクトリメソッド:

まずファクトリとなるBeanを作成し、そのインスタンスのメソッドを呼び出してターゲットBeanを生成します。

public class ServiceFactory {
    public DataService createInstanceService() {
        return new DataService("InstanceService", new DbDataSource());
    }
}
<bean id="serviceFactory" class="com.example.ServiceFactory" />
<bean id="instanceService" factory-bean="serviceFactory" factory-method="createInstanceService" />

4. コレクション型および特殊なプロパティの注入

List、Set、Map、Propertiesなどのコレクション型を注入することも可能です。

List/Setの注入:

<property name="userList">
    <list>
        <value>UserA</value>
        <ref bean="dbSource" />
    </list>
</property>

Mapの注入:

<property name="configMap">
    <map>
        <entry key="timeout" value="3000" />
        <entry key="dataSource" value-ref="dbSource" />
    </map>
</property>

カスタムプロパティエディタ:

Springがデフォルトで変換できない型(例: "yyyy/MM/dd"形式の日付など)を注入する場合、PropertyEditorSupportを継承したクラスを作成し、CustomEditorConfigurerを通じて登録します。

public class DateEditor extends PropertyEditorSupport {
    private String pattern = "yyyy/MM/dd";

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        try {
            this.setValue(sdf.parse(text));
        } catch (ParseException e) {
            throw new IllegalArgumentException("Date format error", e);
        }
    }
}
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="java.util.Date" value="com.example.DateEditor" />
        </map>
    </property>
</bean>

5. ライフサイクルコールバック

Beanの初期化後に特定の処理を実行したい場合はinit-method、コンテナ終了時に破棄処理を行いたい場合はdestroy-methodを指定します。

public class DataService {
    public void init() {
        System.out.println("Initializing Service...");
    }
    public void cleanup() {
        System.out.println("Cleaning up Service...");
    }
}
<bean id="lifecycleService" class="com.example.DataService"
      init-method="init" destroy-method="cleanup" />

アノテーションによる注入

XML設定を補完または代替する手段として、アノテーションを使用した注入が広く利用されています。主要なアノテーションには以下のものがあります。

  • @Autowired: 型(Type)による自動結合を行います。Spring標準のアノテーションです。
  • @Resource: 名前(Name)による自動結合を行います。主にJSR-250準拠のアノテーションです。
  • @Qualifier: 型だけで候補が複数ある場合に、特定のBean名を指定して絞り込みを行います。
  • @Value: プロパティファイルの値やリテラル値を直接フィールドに注入します。

以下の実装例では、@ComponentでクラスをBeanとして登録し、アノテーションで依存関係を解決しています。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;

@Component
public class AnnotationService {
    @Value("AnnotationBasedService")
    private String name;

    // @Autowired + @Qualifier または @Resourceのどちらかを使用
    // @Autowired
    // @Qualifier("primaryDataSource")
    @Resource(name = "primaryDataSource")
    private IDataSource dataSource;

    public void process() {
        System.out.println("Name: " + name + ", Source: " + dataSource.getConnectionInfo());
    }
}

アノテーションの有効化

アノテーションを使用するには、XML設定においてコンポーネントスキャンを有効にする必要があります。<context:component-scan>を使用することで、指定パッケージ以下の@Component@Service@Repositoryなどがアノテーションが付与されたクラスを自動的にBeanとして登録します。また、登録済みのBeanに対してアノテーションによる処理(@Autowiredの解決など)を行うには<context:annotation-config/>も必要ですが、component-scanを使用する場合、この機能は暗黙的に含まれます。

<!-- 指定パッケージ配下のコンポーネントをスキャンしてBean登録とアノテーション処理を有効化 -->
<context:component-scan base-package="com.example" />

タグ: SpringFramework DependencyInjection IoC Java XMLConfiguration

5月18日 11:05 投稿