メソッドインジェクション
ほとんどのアプリケーションシナリオでは、コンテナー内のほとんどの Bean はシングルトンです。シングルトン Bean が別のシングルトン Bean と連携する必要がある場合、または非シングルトン Bean が別の非シングルトン Bean と連携する必要がある場合、通常は、一方の Bean をもう一方の Bean のプロパティとして定義することで依存関係を処理します。Bean のライフサイクルが異なる場合、問題が発生します。シングルトン Bean A が、おそらく A のメソッド呼び出しごとに、非シングルトン (プロトタイプ) Bean B を使用する必要があるとします。コンテナーはシングルトン Bean A を 1 回だけ作成するため、プロパティを設定する機会は 1 回だけです。コンテナーは、Bean A に Bean B の新しいインスタンスが必要になるたびに提供することはできません。
解決策は、制御の逆転を回避することです。ApplicationContextAware
インターフェースを実装し、Bean A が必要とするたびに (通常は新しい) Bean B インスタンスを要求するコンテナーへの getBean("B")
呼び出しを行うことで、Bean A にコンテナーを認識させることができます。次の例は、このアプローチを示しています。
Java
Kotlin
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
package fiona.apple
// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
// A class that uses a stateful Command-style class to perform
// some processing.
class CommandManager : ApplicationContextAware {
private lateinit var applicationContext: ApplicationContext
fun process(commandState: Map<*, *>): Any {
// grab a new instance of the appropriate Command
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.state = commandState
return command.execute()
}
// notice the Spring API dependency!
protected fun createCommand() =
applicationContext.getBean("command", Command::class.java)
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.applicationContext = applicationContext
}
}
ビジネスコードは Spring Framework を認識し、結合しているため、上記は望ましくありません。Spring IoC コンテナーのやや高度な機能であるメソッドインジェクションを使用すると、このユースケースをきれいに処理できます。
ルックアップメソッドインジェクション
ルックアップメソッドインジェクションは、コンテナー管理 Bean のメソッドをオーバーライドし、コンテナー内の別の名前付き Bean のルックアップ結果を返すコンテナーの機能です。前のセクションで説明したシナリオのように、通常、ルックアップにはプロトタイプ Bean が含まれます。Spring Framework は、CGLIB ライブラリからのバイトコード生成を使用してこのメソッドインジェクションを実装し、メソッドをオーバーライドするサブクラスを動的に生成します。
|
前のコードスニペットの CommandManager
クラスの場合、Spring コンテナーは createCommand()
メソッドの実装を動的にオーバーライドします。再加工された例が示すように、CommandManager
クラスには Spring 依存関係はありません。
Java
Kotlin
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
package fiona.apple
// no more Spring imports!
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.state = commandState
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
注入されるメソッド(この場合は CommandManager
)を含むクライアントクラスでは、注入されるメソッドには次の形式の署名が必要です。
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
メソッドが abstract
の場合、動的に生成されたサブクラスがメソッドを実装します。それ以外の場合、動的に生成されたサブクラスは、元のクラスで定義された具象メソッドをオーバーライドします。次の例を考えてみましょう。
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager
として識別される Bean は、myCommand
Bean の新しいインスタンスが必要になるたびに、独自の createCommand()
メソッドを呼び出します。実際に必要な場合は、myCommand
Bean をプロトタイプとしてデプロイするように注意する必要があります。シングルトンの場合は、毎回 myCommand
Bean の同じインスタンスが返されます。
または、次の例に示すように、アノテーションベースのコンポーネントモデル内で、@Lookup
アノテーションを使用してルックアップメソッドを宣言できます。
Java
Kotlin
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup("myCommand")
protected abstract fun createCommand(): Command
}
または、より慣用的に、ターゲット Bean がルックアップメソッドの宣言された戻り型に対して解決されることに依存できます。
Java
Kotlin
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup
protected abstract fun createCommand(): Command
}
通常、抽象クラスがデフォルトで無視される Spring のコンポーネントスキャンルールと互換性を持たせるために、具体的なスタブ実装でこのようなアノテーション付きルックアップメソッドを宣言する必要があります。この制限は、明示的に登録または明示的にインポートされた Bean クラスには適用されません。
スコープの異なるターゲット Bean にアクセスする別の方法は、
|
任意のメソッドの置換
ルックアップメソッドインジェクションよりも有用性の低いメソッドインジェクションは、マネージド Bean の任意のメソッドを別のメソッド実装に置き換える機能です。この機能が実際に必要になるまで、このセクションの残りを安全にスキップできます。
XML ベースの構成メタデータを使用すると、replaced-method
要素を使用して、デプロイされた Bean の既存のメソッド実装を別のメソッド実装に置き換えることができます。computeValue
というメソッドをオーバーライドする次のクラスを検討してください。
Java
Kotlin
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
class MyValueCalculator {
fun computeValue(input: String): String {
// some real code...
}
// some other methods...
}
次の例に示すように、org.springframework.beans.factory.support.MethodReplacer
インターフェースを実装するクラスは、新しいメソッド定義を提供します。
Java
Kotlin
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
class ReplacementComputeValue : MethodReplacer {
override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
// get the input value, work with it, and return a computed result
val input = args[0] as String;
...
return ...;
}
}
元のクラスをデプロイしてメソッドのオーバーライドを指定する Bean 定義は、次の例のようになります。
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<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/>
要素を使用して、オーバーライドされるメソッドのメソッドシグネチャーを示すことができます。引数の署名は、メソッドがオーバーロードされ、クラス内に複数のバリアントが存在する場合にのみ必要です。便宜上、引数の型文字列は完全修飾型名の部分文字列である場合があります。例: 以下はすべて java.lang.String
に一致します:
java.lang.String
String
Str
多くの場合、引数の数はそれぞれの可能な選択肢を区別するのに十分なので、引数型に一致する最短の文字列のみを入力できるようにすることで、このショートカットは多くの入力を節約できます。