Spring IOC三種注入方式(介面注入、setter注入、構造器注入)
什麼是控制反轉?
控制反轉是一種將元件依賴關係的建立和管理置於程式外部的技術。
由容器控制程式之間的關係,而不是由程式碼直接控制
由於控制權由程式碼轉向了容器,所以稱為反轉
物件與物件之間的關係可以簡單的理解為物件之間的依賴關係:
依賴關係:在 A 類需要類 B 的一個例項來進行某些操作,比如在類 A 的方法中需要呼叫類 B 的方法來完成功能,叫做 A 類依賴於B 類。
一個需要特定的依賴的元件一般會涉及一個依賴物件,在 IOC 的概念中叫做目標 (target) 。換句話說, IOC提供了這樣的服務,使一個元件能夠在它的整個生命週期中訪問它的依賴和服務,用這種方法與它的依賴進行互動。總的來說, IOC能夠被分解為兩種子型別:依賴注入和依賴查詢。
(1) 依賴查詢
比如使用 JNDI 註冊一個數據庫連線池的示例中,程式碼中從註冊處獲得依賴關係的 JNDI 查詢 (JNDI lookups):
CODE:
initContext = new InitialContext();
// 獲取資料來源
DataSource ds = (DataSource)initContext.lookup("java:comp/env/jdbc/mysql");
(2) 依賴注入
(Dependency Injection) 依賴注入:兩個物件之間的依賴關係在程式執行時由外部容器動態的注入依賴行為方式稱為依賴注入(DI) 。 DI 是 IOC 的一種形式。
IoC 在應用開發中是一個非常有力的概念。如 Martin Flower 所述, IoC的一種表現形式就是依賴性注射。依賴性注射用的是好萊塢原則, " 不要找我,我會找你的。 "。換句來說,你的類不會去查詢或是例項化它們所依賴的類。控制恰好是反過來的,某種容器會設定這種依賴關係。使用 IoC常常使程式碼更加簡潔,並且為相互依賴的類提供一種很好的方法。
依賴注入的三種實現型別:介面注入、 Setter 注入和構造器注入。
<1> 介面注入 (Type1)
CODE:
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj=Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt()
}
……
}
上面的程式碼中, ClassA 依賴於 InterfaceB 的實現,如何獲得 InterfaceB實現類的例項?傳統的方法是在程式碼中建立 InterfaceB 實現類的例項,並將起賦予 clzB 。
而這樣一來, ClassA 在編譯期即依賴於 InterfaceB的實現。為了將呼叫者與實現者在編譯期分離,於是有了上面的程式碼.
我們根據預先在配置檔案中設定的實現類的類名 (Config.BImplementation) ,動態載入實現類,並通過InterfaceB 強制轉型後為 ClassA 所用。這就是介面注入的一個最原始的雛形。
而對於一個 Type1 型 IOC 容器而言,載入介面實現並建立其例項的工作由容器完成,見如下程式碼。
CODE:
public class ClassA {
private InterfaceB clzB;
public Object doSomething(InterfaceB b) {
clzB = b;
return clzB.doIt();
}
……
}
在執行期, InterfaceB 例項將由容器提供。
Type1 型 IOC 發展較早(有意或無意),在實際中得到了普遍應用,即使在 IOC的概念尚未確立時,這樣的方法也已經頻繁出現在我們的程式碼中。
下面的程式碼大家應該非常熟悉:
CODE:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponseresponse)throws ServletException, IOException { …… } }
這也是一個 Type1 型注入, HttpServletRequest 和 HttpServletResponse 例項由Servlet Container 在執行期動態注入。
<2>Setter( 設值 ) 注入 (Type2)
各種型別的依賴注入模式中,設值注入模式在實際開發中得到了最廣泛的應用。
CODE:
public class ClassA {
private InterfaceB clzB;
public void setClzB(InterfaceB clzB) {
this. clzB = clzB; }
……
}
<3> 構造器注入 (Type3)
依賴關係是通過類建構函式建立的
容器通過呼叫類的構造方法將其所需的依賴關係注入其中
CODE:
public class DIByConstructor { private final DataSourcedataSource;
public DIByConstructor(DataSource ds) {
this.dataSource = ds;
}
……
}
<4> 三種依賴注入方式的比較
介面注入:
介面注入模式因為歷史較為悠久,在很多容器中都已經得到應用。但由於其在靈活性、易用性上不如其他兩種注入模式,因而在 IOC的專題世界內並不被看好。
Setter 注入:
對於習慣了傳統 javabean 開發的程式設計師,通過 setter 方法設定依賴關係更加直觀。
如果依賴關係較為複雜,那麼構造子注入模式的建構函式也會相當龐大,而此時設值注入模式則更為簡潔。
如果用到了第三方類庫,可能要求我們的元件提供一個預設的建構函式,此時構造子注入模式也不適用。
構造器注入:
在構造期間完成一個完整的、合法的物件。
所有依賴關係在建構函式中集中呈現。
依賴關係在構造時由容器一次性設定,元件被建立之後一直處於相對“不變”的穩定狀態。
只有元件的建立者關心其內部依賴關係,對呼叫者而言,該依賴關係處於“黑盒”之中。
總結:
可見, Type3 和 Type2 模式各有千秋,而 Spring 、 PicoContainer 都對 Type3 和 Type2型別的依賴注入機制提供了良好支援。這也就為我們提供了更多的選擇餘地。理論上,以 Type3 型別為主,輔之以 Type2型別機制作為補充,可以達到最好的依賴注入效果,不過對於基於 Spring Framework 開發的應用而言, Type2使用更加廣泛