貳-spring的基本概念

Dependency Injection

何謂控制反轉(IoC = Inversion of Control),何謂依賴注入(DI = Dependency Injection)?

IoC,用白話來講,就是由容器控制程序之間的關係,而非傳統實現中,由程序代碼直接操控。這也就是所謂“控制反轉”的概念所在:控制權由應用代碼中轉到了外部容器,控制權的轉移,是所謂反轉。

建議各位讀者將IoC和DI 放在一起理解,DI是IoC的另外一種更形象的說法

 

依賴注入,何用之有?

依賴注入的目標並非爲軟件系統帶來更多的功能,而是爲了提升組件重用的概率,併爲系統搭建一個靈活、可擴展的平臺。回顧《壹-初識spring及爲什麼要用spring》中的UpperAction/LowerAction 在運行前,其 Message 節點爲空。運行後由容器將字符串“HeLLo”注入。此時 UpperAction/LowerAction 即與內存中的“HeLLo”字符串對象建立了依賴關係。

對比傳統的實現方式(如通過編碼初始化Message),我們可以看到,基於依賴注入的系統實現相當靈活簡潔。

通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定UpperAction/LowerAction中所需的Message實例。UpperAction/LowerAction只需利用容器注入的Message實際值,完成自身的業務邏輯,而不用關心具體的資源來自何處、由誰實現。

依賴注入機制減輕了組件之間的依賴關係,同時也大大提高了組件的可移植性,這意味着,組件得到重用的機會將會更多。

 

依賴注入的幾種實現類型

 

1、Type1 接口注入

 

我們常常藉助接口來將調用者與實現者分離。如:


public class ClassA {

    private InterfaceB clzB;

    public init() {

    
    jbect obj = Class.forName(Config.BImplementation).newInstance();

    clzB = (InterfaceB)obj;

    }

    ……

}

上面的代碼中,ClassA依賴於InterfaceB的實現,如何獲得InterfaceB實現類的實例?傳統的方法是在代碼中創建InterfaceB實現類的實例,並將起賦予clzB。

而這樣一來,ClassA在編譯期即依賴於InterfaceB的實現。爲了將調用者與實現者在編譯期分離,於是有了上面的代碼,我們根據預先在配置文件中設定的實現類的類名,動態加載實現類,並通過InterfaceB強制轉型後爲ClassA所用。

這就是接口注入的一個最原始的雛形。

而對於一個Type1型IOC容器而言,加載接口實現並創建其實例的工作由容器完成,如J2EE開發中常用的

Context.lookup(ServletContext.getXXX),都是Type1型IOC的表現形式。

Apache Avalon是一個典型的Type1型IOC容器。

 

2、Type2 構造子注入

構造子注入,即通過構造函數完成依賴關係的設定,如:

public class DIByConstructor {


    private final DataSource dataSource;

    private final String message;

    public DIByConstructor(DataSource ds, String msg) { 

    this.dataSource = ds;

    this.message = msg;

    }

    ……

}

可以看到,在Type2類型的依賴注入機制中,依賴關係是通過類構造函數建立,容器通過調用類的構造方法,將其所需的依賴關係注入其中。

PicoContainer(另一種實現了依賴注入模式的輕量級容器)首先實現了Type2類型的依賴注入模式。

 

3、Type3 設值注入

在各種類型的依賴注入模式中,設值注入模式在實際開發中得到了最廣泛的應用(其中很大一部分得力於Spring框架的影響)。

在筆者看來,基於設置模式的依賴注入機制更加直觀、也更加自然。Quick Start中的示例,就是典型的設置注入,即通過類的setter方法完成依賴關係的設置。

幾種依賴注入模式的對比總結

接口注入模式因爲具備侵入性,它要求組件必須與特定的接口相關聯,因此並不被看好,實際使用有限。

Type2和Type3的依賴注入實現模式均具備無侵入性的特點。在筆者看來,這兩種實現方式各有特點,也各具優勢

 

Type2 構造子注入的優勢:

 

1. “在構造期即創建一個完整、合法的對象”,對於這條Java設計原則,Type2無疑是最好的響應者。

2. 避免了繁瑣的setter方法的編寫,所有依賴關係均在構造函數中設定,依賴關係集中呈現,更加易讀。

3. 由於沒有setter方法,依賴關係在構造時由容器一次性設定,因此組件在被創建之後即處於

相對“不變”的穩定狀態,無需擔心上層代碼在調用過程中執行setter方法對組件依賴關係

產生破壞,特別是對於Singleton模式的組件而言,這可能對整個系統產生重大的影響。

4. 同樣,由於關聯關係僅在構造函數中表達,只有組件創建者需要關心組件內部的依賴關係。對調用者而言,組件中的依賴關係處於黑盒之中。對上層屏蔽不必要的信息,也爲系統的層次清晰性提供了保證。

5. 通過構造子注入,意味着我們可以在構造函數中決定依賴關係的注入順序,對於一個大量依賴外部服務的組件而言,依賴關係的獲得順序可能非常重要,比如某個依賴關係注入的先決條件是組件的DataSource及相關資源已經被設定。

 

Type3 設值注入的優勢

 

1. 對於習慣了傳統JavaBean開發的程序員而言,通過setter方法設定依賴關係顯得更加直觀,更加自然。

2. 如果依賴關係(或繼承關係)較爲複雜,那麼Type2模式的構造函數也會相當龐大(我們需要在構造函數中設定所有依賴關係),此時Type3模式往往更爲簡潔。

3. 對於某些第三方類庫而言,可能要求我們的組件必須提供一個默認的構造函數(如Struts中的Action),此時Type2類型的依賴注入機制就體現出其侷限性,難以完成我們期望的功能。

可見,Type2和Type3模式各有千秋,而Spring、PicoContainer都對Type2和Type3類型的依賴注入機制提供了良好支持。這也就爲我們提供了更多的選擇餘地。理論上,以Type2類型爲主,輔之以Type3類型機制作爲補充,可以達到最好的依賴注入效果,不過對於基於Spring Framework開發的應用而言,Type3使用更加廣泛。

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