方法注入
在大多數應用場景中,容器中的大部分 bean 都是單例。當一個單例 bean 需要與另一個單例 bean 協作,或者一個非單例 bean 需要與另一個非單例 bean 協作時,通常透過將一個 bean 定義為另一個 bean 的屬性來處理依賴關係。當 bean 的生命週期不同時,就會出現問題。假設單例 bean A 需要使用非單例(原型)bean B,可能在每次呼叫 A 的方法時都需要。容器只建立一次單例 bean A,因此只有一次機會設定屬性。容器無法在每次需要時為 bean A 提供 bean B 的新例項。
一個解決方案是放棄一些控制反轉。你可以透過實現 ApplicationContextAware 介面讓 bean A 知道容器,並透過每次 bean A 需要時呼叫 getBean("B") 向容器請求一個(通常是新的)bean B 例項。以下示例展示了這種方法
-
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 框架。方法注入是 Spring IoC 容器的一個稍高階的特性,它能讓你乾淨地處理這個用例。
查詢方法注入
查詢方法注入是容器覆蓋容器管理 bean 上的方法並返回容器中另一個命名 bean 的查詢結果的能力。查詢通常涉及原型 bean,如上一節中描述的場景。Spring 框架透過使用 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
}
|
訪問不同作用域目標 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/> 元素中使用一個或多個 <arg-type/> 元素來指示被覆蓋方法的簽名。僅當方法被過載且類中存在多個變體時,引數的簽名才必需。為方便起見,引數的型別字串可以是完全限定型別名稱的子字串。例如,以下所有都匹配 java.lang.String
java.lang.String
String
Str
由於引數的數量通常足以區分每個可能的選擇,因此此快捷方式可以節省大量輸入,讓你只需鍵入與引數型別匹配的最短字串。