1 IoC 容器
1.4 依賴
1.4.6 方法注入
在大多數應用程序場景中,容器中的大多數bean都是單例的。當一個單例bean需要與另一個單例bean協作,或者一個非單例bean需要與另一個非單例bean協作時,通常通過將一個bean定義爲另一個bean的屬性來處理依賴性。當bean的生命週期不同時,就會出現問題。假設單例bean A需要使用非單例(原型)bean B,可能是在A的每個方法調用上。容器只創建一次單例bean,因此只得到一次設置屬性的機會。容器不能實現每當Bean A 需要新的Bean B實例時,都能向Bean A 提供一個Bean B實例。
解決的辦法是放棄一些控制反轉。您可以通過實現applicationcontextAware接口,並在每次bean A需要時調用容器的getBean(“B”)來請求(通常是一個新的)bean B實例,從而使bean A知道容器。下面的例子展示了這種方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
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) {
// 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;
}
}
但是上面的方法是不可取的,因爲業務代碼知道Spring框架並與之耦合。方法注入是Spring IoC容器的一個比較高級的特性,它允許您乾淨地處理這個用例。
您可以在這篇博客中瞭解更多關於方法注入的動機。
查找方法注入
查找方法注入是容器覆蓋容器管理bean上的方法並返回容器中另一個命名bean的查找結果的能力。查找通常涉及到一個prototype bean,如前一節描述的場景所示。Spring框架通過使用來自CGLIB庫的字節碼生成來動態生成覆蓋該方法的子類,從而實現了這種方法注入。
- 要使這個動態子類工作,Spring bean容器子類的類不能是final,要覆蓋的方法也不能是final。
- 對具有抽象方法的類進行單元測試需要您自己對該類進行子類化,並提供抽象方法的存根實現。
- 組件掃描也需要具體的方法,這需要具體的類來獲取。
- 進一步的關鍵限制是,查找方法不能與工廠方法一起工作,特別是不能與配置類中的@Bean方法一起工作,因爲在這種情況下,容器不負責創建實例,因此不能動態地創建運行時生成的子類。
在前面代碼片段中的CommandManager類的情況下,Spring容器動態地覆蓋createCommand()方法的實現。CommandManager類沒有任何Spring依賴項,如重新處理的示例所示:
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();
}
在包含要注入的方法的client類中(本例中的CommandManager),要注入的方法需要以下形式的簽名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,則動態生成的子類實現該方法。否則,動態生成的子類將覆蓋在原始類中定義的具體方法。考慮下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
標識爲commandManager的bean在需要myCommand bean的新實例時調用自己的createCommand()方法。如果實際需要的話,您必須小心地將myCommand bean部署爲 prototype 。如果它是singleton,的,那麼每次都會返回相同的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的另一種方法是ObjectFactory / Provider注入點。請參考Scoped Beans as Dependencies。
ServiceLocatorFactoryBean(在org.springframework.beans.factory.config中)也是很有用的
任意的方法替換
與查找方法注入相比,一種不太有用的方法注入形式是能夠用另一種方法實現替換託管bean中的任意方法。您可以安全地跳過本節的其餘部分,直到您真正需要此功能爲止。
對於基於xml的配置元數據,您可以使用replace-method元素將已部署bean的現有方法實現替換爲其他方法實現。考慮下面的類,它有一個我們想要覆蓋的叫做computeValue的方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
實現org.springframe.bean.factory.support的類。MethodReplacer接口提供了新的方法定義,如下例所示:
/**
* 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 ...;
}
}
部署原始類並指定方法覆蓋的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"/>
您可以在< replacedmethod />元素中使用一個或多個< argtype />元素來指示被覆蓋方法的方法簽名。只有在方法重載且類中存在多個變量時,才需要對參數進行簽名。
爲了方便起見,參數的類型字符串可以是完全限定類型名稱的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因爲參數的數量通常足以區分每種可能的選擇,所以這個快捷方式可以節省大量的輸入,因爲它允許您只輸入與參數類型匹配的最短字符串。