1. 程式人生 > >Spring 核心技術(5)

Spring 核心技術(5)

接上篇:Spring 核心技術(4)

version 5.1.8.RELEASE

1.4.5 自動裝配協作者

Spring 容器可以自動連線協作 bean 之間的關係。你可以讓 Spring 通過檢查 ApplicationContext 中的內容自動為 bean 解析協作者(其他bean)。自動裝配具有以下優點:

  • 自動裝配可以顯著減少指定屬性或建構函式引數的需要。(在本章其他地方討論的其他機制,如bean模板 ,在這方面也很有價值。)
  • 自動裝配可以隨著物件的演變更新配置。例如,如果需要向類新增依賴項,自動裝配可以自動滿足該依賴項,而無需修改配置。因此,自動裝配在開發期間尤其有用,在程式碼庫變得更穩定後也不會取消切換到顯式裝配的選項。

使用基於 XML 的配置元資料(請參閱依賴注入)時,可以使用 <bean/> 元素的 autowire 屬性為 bean 定義指定裝配模式。自動裝配功能有四種模式。你指定每個 bean 的自動裝配,因此可以選擇要自動裝配哪些。下表描述了四種自動裝配模式:

模式 說明
no (預設)無自動裝配。Bean 引用必須由 ref 元素定義。在進行較大部署更新時不建議更改預設設定,因為明確指定協作者可以提供更好的控制和清晰度。在某種程度上,它記錄了系統的結構。
byName 按屬性名稱自動裝配。Spring 查詢與需要自動裝配的屬性同名的 bean。例如,如果 bean 定義按名稱設定為根據名稱進行注入並且它包含一個 master
屬性(即,它有一個 setMaster(..) 方法),則 Spring 會查詢名為 master 的 bean 定義並使用它來設定屬性。
byType 如果容器中只存在一個該屬性型別的 bean,則允許屬性自動裝配。如果存在多個,則丟擲致命異常,這表示可能不能對該 bean 使用 byType 自動裝配。如果沒有匹配的 bean,則不會發生任何事情(該屬性未設定)。
constructor 類似於 `byType 但適用於建構函式引數。如果容器中沒有建構函式引數型別的一個 bean,則會引發致命錯誤。

使用 byTypeconstructor 自動裝配模式,可以裝配陣列和型別化的集合。在這種情況下,容器中所有與預期型別匹配的自動裝配候選者都會被提供以滿足依賴性。如果預期的鍵型別是 String

,則可以自動裝配強型別 Map 例項。自動裝配 Map 例項的值由與預期型別匹配的所有 bean 例項組成, Map 例項的鍵包含相應的 bean 名稱。

自動裝配的侷限和缺點

當在整個專案中使用一致的自動裝配時,自動裝配效果最佳。如果一般不使用自動裝配,那麼開發人員使用它來裝配一個或兩個 bean 定義可能會讓人感到困惑。

考慮自動裝配的侷限和缺點:

  • propertyconstructor-arg 中設定的顯式依賴項始終覆蓋自動裝配。不能自動裝配簡單屬性,例如基本型別,Strings,和 Classes(以及此類簡單屬性的陣列)。這是設計中的限制。
  • 自動裝配不如顯式裝配精確。雖然如前面的表中所述,但 Spring 會謹慎地避免在帶有歧義時進行猜測,這可能出現預料之外的結果。Spring 管理的物件之間的關係不再明確記錄。
  • 從 Spring 容器中獲取文件的工具可能無法獲取自動裝配資訊。
  • 容器中的多個 bean 定義可以匹配 setter 方法或建構函式引數指定的型別以進行自動裝配。對於陣列,集合或 Map 例項,這不一定是個問題。但是,對於期望單個值的依賴關係,這種模糊性不是可以隨意解決的。如果沒有可用的唯一 bean 定義,則丟擲異常。

在後一種情況下,您有幾種選擇:

  • 放棄自動裝配,使用持顯式裝配。
  • 如下一節所述,通過將其 autowire-candidate 屬性設定為 false,可以避免對bean定義進行自動裝配。
  • 通過將其 <bean/> 元素的 primary 屬性設定為 true,將單個 bean 定義指定為主要候選者。
  • 基於註解的配置項實現更細粒度的控制元件,如基於註解的容器配置中所述。

從自動裝配中排除 Bean

基於每個 bean,你可以從自動裝配中排除 bean。在 Spring 的 XML 格式中,將 <bean/> 元素的 autowire-candidate 屬性設定為 false。容器使指定的 bean 定義對自動裝配不可用(包括註解配置,例如 @Autowired)。

autowire-candidate 屬性旨在僅影響基於型別的自動裝配。它不會影響根據名稱的顯式引用,即使指定的 bean 未標記為自動註解的候選人,也會解析它。因此,如果名稱匹配,則按名稱自動裝配會注入 bean。

你還可以根據 bean 名稱的表示式匹配來限制自動裝配候選項。頂級的 <beans/> 元素在 default-autowire-candidates 屬性中接受一個或多個表示式 。例如,要將自動裝配候選狀態限制為名稱以 Repository 結尾的任何 bean,那麼就提供值 *Repository。需要提供多個表示式時,請在列表中定義並使用逗號分隔。bean 定義的 autowire-candidate 屬性優先使用顯示定義的值 truefalse。對於此類 bean,表示式匹配規則並不適用。

這些技術對於永遠不希望通過自動裝配注入其他 bean 的 bean 非常有用。這並不意味著排除的 bean 本身不能使用自動裝配進行配置。相反,bean 本身不是其他 bean 自動裝配的候選者。

1.4.6 方法注入

在大多數應用程式場景中,容器中的大多數 bean 都是單例。當單例 bean 需要與另一個單例 bean 協作或非單例 bean 需要與另一個非單例 bean 協作時,通常通過將一個 bean 定義為另一個 bean 的屬性來處理依賴關係。當 bean 的生命週期不同時會出現問題。假設單例 bean A 需要使用非單例(原型)bean B,可能是在 A 上的每個方法呼叫上。容器只建立一次單例 bean A,因此只有一次機會來設定屬性。容器不能在 bean A 每次需要時提供一個新的 bean B 的例項。

解決方案是放棄一部分控制反轉。你可以通過實現 ApplicationContextAware 介面使 bean A 對容器可見,同時通過讓容器呼叫 getBean("B") 在每次需要時請求 bean B 的例項。以下示例展示了此方法:

// 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 Framework。方法注入是Spring IoC容器的一個高階功能,可以更加輕鬆地處理這種場景。

你可以在此部落格中閱讀有關方法注入成因的更多資訊。

查詢方法注入

查詢方法注入是容器重寫容器管理的 bean 中的方法併為容器中另外一個命名的 bean 返回查詢結果的能力。查詢通常涉及原型 bean,如上一節中描述的場景。Spring Framework 通過使用 CGLIB 庫中的位元組碼生成來動態生成覆蓋該方法的子類來實現此方法注入。

  • 要使這個動態子類工作,Spring bean 容器子類不能設定為 final,要重寫的方法也不能設定為 final
  • 對具有 abstract 方法的類進行單元測試需要你自己對類進行子類化並提供該 abstract 方法的 stub 實現。
  • 元件掃描也需要具體的方法,這需要具體的類來獲取。
  • 另一個關鍵限制是查詢方法不適用於工廠方法,特別是 @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();
}

在包含要注入的方法的客戶端類中(本例中的 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>

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

只要需要 myCommand bean 的新例項,定義為 commandManager 的 bean 就會呼叫自己的 createCommand() 方法。如果實際需要,必須注意將 myCommand 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();
}

請注意,通常應該使用具體的 stub 實現來宣告這種帶註釋的查詢方法,以使它們與 Spring 的元件掃描規則相容,預設情況下抽象類會被忽略。此限制不適用於顯式註冊或顯式匯入的 bean 類。

訪問不同作用域的目標 bean 的另一種方法是 ObjectFactory/Provider 注入點。請參閱使用 Scoped Beans 作為依賴關係。

你可能還會發現 ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config 包中)有用。

任意方法替換

與查詢方法注入相比,一種不太有用的方法注入形式是能夠使用另一個方法實現替換託管 bean 中的任意方法。你可以安全地跳過本節的其餘部分,直到確實需要此功能。

使用基於 XML 的配置元資料時,可以使用 replaced-method 元素將已存在的方法實現替換為已部署的 bean。考慮以下類,它有一個我們想要覆蓋的 computeValue 方法:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

實現 org.springframework.beans.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"/>

你可以使用一個或多個 <replaced-method/> 元素的 <arg-type/> 元素來表明被覆蓋的方法的方法簽名。僅當方法過載且類中存在多個變體時,才需要引數的簽名。為方便起見,引數的型別字串可以是完全限定型別名稱的子字串。例如,以下所有均匹配 java.lang.String

java.lang.String
String
Str

因為引數的數量通常足以區分每個可能的選擇,所以通過讓您只鍵入與引數型別匹配的最短字串,此快捷方式可以節省大量的輸入。

  • 我的CSDN:https://blog.csdn.net/liweitao7610
  • 我的部落格園:https://www.cnblogs.com/aotian/
  • 我的簡書:https://www.jianshu.com/u/6b6e162f1fdc