Spring Framework Core(1)-The Ioc Container(4)

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

因爲參數的數量通常足以區分每種可能的選擇,所以這個快捷方式可以節省大量的輸入,因爲它允許您只輸入與參數類型匹配的最短字符串。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章