Springにおけるメソッドインジェクション

多くのアプリケーションシナリオでは、ほとんどのBeanはシングルトンとして扱われます。シングルトンBeanが別のシングルトンBeanまたは非シングルトンBeanと連携する必要がある場合、開発者は単に依存BeanをそのBeanのプロパティとして設定すれば済みます。しかし、Beanのライフサイクルが異なるために問題が発生することがあります。シングルトンBean Aが、メソッド呼び出しごとにステートフルなBean Bを使用する必要があると仮定します。コンテナはBean Aを一度だけ作成し、プロパティを設定する機会は一度だけです。この場合、コンテナはBean Aが作成されるたびに新しいBean Bのインスタンスを提供できません。

この問題に対する1つの解決策は、IoCを放棄することです。開発者はApplicationContextAwareインターフェイスを実装することで、Bean AをApplicationContextから認識できるようにすることができます。これにより、Bean Aが必要なときにgetBean("B")を呼び出して、新しいBean Bのインスタンスを取得できます。


package fiona.apple;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // 新しいCommandインスタンスを取得
        Command command = createCommand();
        // (新品であるはずの)Commandインスタンスに状態を設定
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // Spring APIへの依存に注意!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

ルックアップメソッドインジェクション

ルックアップメソッドインジェクションは、コンテナが管理Beanのメソッドをオーバーライドして、コンテナ内の別の名前付きBeanのルックアップ結果を返す機能です。ルックアップメソッドは通常、プロトタイプBeanに関連します。Springフレームワークは、CGLIBライブラリを使用して生成されたバイトコードにより、動的なサブクラスを作成してメソッドをオーバーライドすることで、このインジェクションを実現します。

この動的サブクラスが正常に機能するためには、Springコンテナによって継承されるBeanはfinalではなく、オーバーライドされるメソッドもfinalであってはなりません。

抽象メソッドを持つクラスの単体テストでは、開発者はクラスをサブクラス化し、抽象メソッドの具体的な実装を提供する必要があります。

コンポーネントスキャンも、具体的なクラスを取得するために具体的なメソッドを必要とします。

もう1つの重要な制限は、ルックアップメソッドがファクトリメソッドには適用されないことです。特に、設定クラスで@Beanメソッドを使用しない場合に該当します。これらの場合、コンテナはインスタンス作成を担当しないため、実行時に生成されたサブクラスをインスタンス化できません。

前のコードスニペットのCommandManagerクラスの場合、SpringコンテナはcreateCommand()メソッドの実装を動的にオーバーライドします。CommandManagerクラスは、Springへの依存関係を持たなくなります。


package fiona.apple;

// Springインポートは不要!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // 新しいCommandインターフェイスのインスタンスを取得
        Command command = createCommand();
        // (新品であるはずの)Commandインスタンスに状態を設定
        command.setState(commandState);
        return command.execute();
    }

    // しかし、このメソッドの実装はどこにあるのか?
    protected abstract Command createCommand();
}

メソッドをインジェクトする必要があるクライアントクラス(この例ではCommandManager)では、インジェクトされるメソッドのシグネチャは次の形式である必要があります。


<public|protected> [abstract] <return-type> theMethodName(no-arguments);

メソッドがabstractの場合、動的に生成されたサブクラスがそのメソッドを実装します。それ以外の場合、動的に生成されたサブクラスは、元のクラスで定義された具体的なメソッドをオーバーライドします。


<!-- プロトタイプ (非シングルトン) としてデプロイされたステートフルなBean -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- 必要に応じて依存関係をインジェクト -->
</bean>

<!-- commandProcessor は statefulCommandHelper を使用 -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

commandManagerとして識別されるBeanは、新しいmyCommand Beanインスタンスが必要になると、自身のcreateCommand()メソッドを呼び出します。開発者はmyCommand BeanをプロトタイプBeanとして慎重にデプロイする必要があります。必要なBeanがシングルトンである場合、毎回同じmyCommand Beanインスタンスが返されます。

また、アノテーションベースの設定モードの場合、ルックアップメソッドに@Lookupアノテーションを定義することもできます。


public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

または、より一般的には、ルックアップメソッドの戻り値の型に基づいて一致するBeanを検索することもできます。


public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

開発者は、ルックアップメソッドをサブクラス化することで、Springのコンポーネントスキャン規則と互換性を持たせることができます。デフォルトでは、抽象クラスは無視されます。この制限は、明示的にBeanを登録したり、Beanを明示的にインポートしたりする場合には適用されません。

異なるライフサイクルにアクセスするもう1つの方法は、ObjectFactory/Providerインジェクションです。

ServiceLocatorFactoryBeanorg.springframework.beans.factory.configパッケージ)も役立つ場合があります。

任意のメソッドの置換

前述の説明から、ルックアップメソッドがコンテナによって管理される任意のBeanメソッドをオーバーライドする機能を持つことがわかります。開発者は、この機能が本当に必要な場合を除き、このセクションをスキップすることをお勧めします。

XMLベースのメタデータ設定を使用して、replaced-method要素を使用して既存のメソッドの実装を置換できます。ここでは、computeValueという名前のメソッドをオーバーライドしたいクラスを検討します。


public class MyValueCalculator {

    public String computeValue(String input) {
        // 実際のコード...
    }

    // その他のメソッド...
}

org.springframework.beans.factory.support.MethodReplacerインターフェイスを実装するクラスは、新しいメソッド定義を提供します。


/**
 * MyValueCalculator の既存の computeValue(String) 実装を
 * オーバーライドするために使用されます。
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // 入力値を取得し、処理してから、計算結果を返します
        String input = (String) args[0];
        ...
        return ...;
    }
}

Beanメソッドをオーバーライドする必要がある場合のXML設定は、次の例に似ています。


<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- 任意のメソッド置換 -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

<replaced-method/>要素では、1つ以上の<arg-type/>要素を使用して、オーバーライドされるメソッドの引数型を指定できます。オーバーライドされるメソッドにオーバーロードが存在する場合、必要な引数を指定する必要があります。便宜上、String型は次の型と一致し、java.lang.Stringと完全に同等です。


java.lang.String
String
Str

引数の数が異なるメソッドを区別するのに十分な場合が多いため、このショートカット表記は多くのコードを節約できます。

タグ: Spring DI ルックアップメソッドインジェクション メソッド置換

7月5日 17:19 投稿