天天看點

第二章 深入探讨控制反轉(Ioc)和依賴注入(DI)之二

  以下這部分是第二章後面的.

(2)IOC 是一種使應用程式邏輯外在化的設計模式

因為提供服務的元件是被注入而不是被寫入到客戶機代碼中。将 IOC 與接口程式設計應用結合進而産生出 Spring 架構的架構,這種架構能夠減少客戶機對特定實作邏輯的依賴。

(3)IoC的設計目标

不建立對象,但是描述建立它們的方式。在代碼中不直接與對象和服務連接配接,但在配置檔案中描述哪一個元件需要哪一項服務。容器(在 Spring 架構中是 IOC 容器) 負責将這些聯系在一起。

(4)IoC在應用開發中的展現

IoC的抽象概念是“依賴關系的轉移”,在實際應用中的下面的各個規則其實都是IoC在應用開發中的展現。

l  “高層子產品元件不應該依賴低層子產品元件,而是子產品元件都必須依賴于抽象”是 IoC的一種表現

l  “實作必須依賴抽象,而不是抽象依賴實作”也是IoC的一種表現

l  “應用程式不應依賴于容器,而是容器服務于應用程式”也是IoC的一種表現。

接下來我們講在講述它的另外的一個名字:依賴注入(DI)。

Spring 中的依賴注入(DI)

(1)DI = Dependency Injection

正在業界為IoC争吵不休時《Inversion of Control Containers and the Dependency Injection pattern》為IoC正名,至此,IoC又獲得了一個新的名字:“依賴注入(Dependency Injection)”。

Dependency Injection模式是依賴注射的意思,也就是将依賴先剝離,然後在适當時候再注射進入。

(2)何謂依賴注入

相對IoC 而言,“依賴注入”的确更加準确地描述了這種古老而又時興的設計理念。從名字上了解,所謂依賴注入,即元件之間的依賴關系由容器在運作期決定,形象的來說,即由容器動态的将某種依賴關系注入到元件之中。

講的通俗點,就是在運作期,由Spring根據配置檔案,将其他對象的引用通過元件的提供的setter方法或者構造方法等進行設定。

在上面的UserService中已經展現了這種方式,當UserService需要的UserLogin的時候,容器會給它注入。這就展現了需要用的時候,有容器給你注入。你不必主動的如建立了。也不用如管理它了。是不是和以前的程式設計方式有些改變。可能你會想到,這不就是工廠模式的衍生嗎?不錯它就是利用工廠模式的原理實作的,但它原比工廠模式簡單。通過上面的部分代碼我們就可以看出它就是工廠模式的衍生,BeanFactory就充分說明了這一點。

我接下來通過講一個比較接近生活中的例子來說名依賴注入的原理。

圖解“依賴注入”(摘錄網上資料)

(1)IT人員的标準“行頭”

上面是我們常用的工作裝備,筆記本電腦一台、USB硬碟和U盤各一隻。想必大家在日常工作中也有類似的一套行頭。這與依賴注入有什麼關系?

(2)圖解“依賴注入”---在運作時由容器将依賴關系注入到元件中

圖中三個裝置都有一個共同點,都支援USB 接口。當我們需要将資料複制到外圍儲存設備時,可以

根據情況,選擇是儲存在U盤還是USB硬碟,下面的操作大家也都輕車熟路,無非接通USB接口,然後在資源浏覽器中将標明的檔案拖放到指定的盤符。

這樣的操作在過去幾年中每天都在我們身邊發生,而這也正是所謂依賴注入的一個典型案例,再看上例中,筆記本電腦與外圍儲存設備通過預先指定的一個接口(USB)相連,對于筆記本而言,隻是将使用者指定的資料發送到USB接口,而這些資料何去何從,則由目前接入的USB裝置決定。

在USB裝置加載之前,筆記本不可能預料使用者将在USB接口上接入何種裝置,隻有USB裝置接入之後,這種裝置之間的依賴關系才開始形成。

對應上面關于依賴注入機制的描述,在運作時(系統開機,USB 裝置加載)由容器(運作在筆記本中的Windows作業系統)将依賴關系(筆記本依賴USB裝置進行資料存取)注入到元件中(Windows檔案通路元件)。這就是依賴注入模式在現實世界中的一個版本。

在Spring中為什麼要提供“依賴注入”設計理念

(1)目的

依賴注入的目标并非為軟體系統帶來更多的功能,而是為了提升元件重用的機率,并為系統搭建一個靈活、可擴充的平台。

(2)原因---更簡潔的程式設計實作

很多初學者常常陷入"依賴注入,何用之有?"的疑惑。想來前面和下面的例子可以幫助大家簡單的了解其中的含義。

回顧上面建立Spring_chap2的例子中,UserService類在運作前,其userName,passWord節點為空。運作後由容器将字元串"admin"和"1234"注入。此時UserService即與記憶體中的"admin"和"1234"字元串對象建立了依賴關系。也許區區一個字元串我們無法感受出依賴關系的存在。

如果把這裡的userName/passWord屬性換成一個資料源(DataSource),可能更有感覺:

<beans>

<bean id="dataSource" class="org.springframework.indi.JndiObjectFactoryBean">

                    <property name="jndiName">

連接配接池的配置交給容器                     <value> java:/comp/env/jdbc/testDB</value>

        </property>

</bean>

<bean id="dataBean" class="examples.DAOBean">

                    <property name="dataSource">

                     <ref bean="dataSource"/>

</beans>

其中DAOBean(假設DAOBean是一個運作在J2EE容器中的元件---如Weblogic或者Tomcat等)中的dataSource将由容器在運作期動态注入,而DataSource的具體配置和初始化工作也将由容器在運作期完成。

對比傳統的實作方式(如通過編碼初始化DataSource執行個體),我們可以看到,基于依賴注入的系統實作相當靈活簡潔。

(3)産生的效果

通過依賴注入機制,我們隻需要通過簡單的配置,而無需任何代碼就可指定DAOBean中所需的DataSource執行個體。DAOBean隻需利用容器注入的DataSource執行個體,完成自身的業務邏輯,而不用關心具體的資源來自何處、由誰實作。

l  提高了元件的可移植性和可重用度

假設我們的部署環境發生了變化,系統需要脫離應用伺服器獨立運作,這樣,由于失去了容器的支援,原本通過JNDI擷取DataSource的方式不再有效(因為,現在則需要改變為由某個元件直接提供DataSource)。

我們需要如何修改以适應新的系統環境?很簡單,我們隻需要修改dataSource的配置:

<bean id="dataSource" class=" org.apache.commons.dbcp.BasicDataSource " destroy-method="close">

連接配接池的配置交給bean好,容易遷移

        <property name="driverClassName">

            <value>com.microsoft.jdbc.sqlserver.SQLServerDriver</value>

        <property name="url">

            <value>jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=WebStudyDB</value>

        <property name="username">

            <value>sa</value>

        <property name="password">

            <value>1234</value>

這裡我們的DataSource改為由Apache DBCP元件提供。沒有編寫任何代碼我們即實作了DataSource的切換。

l  依賴注入機制減輕了元件之間的依賴關系

回想傳統編碼模式中,如果要進行同樣的修改,我們需要付出多大的努力。是以,依賴注入機制減輕了元件之間的依賴關系,同時也大大提高了元件的可移植性,這意味着,元件得到重用的機會将會更多。

接着我會在講一個更現實的生活中的例子。

對IoC的另一種解釋示例---生活中找“對象”

(1)控制倒(反)轉

l  正常的方式---自己戀愛

舉個簡單的例子,我們是如何找女朋友的?常見的情況是,我們到處去看哪裡有長得漂亮身材又好的mm,然後打聽她們的興趣愛好、qq号、電話号、ip号、iq号………,想辦法認識她們,投其所好送其所要,然後嘿嘿……這個過程是複雜深奧的,我們必須自己設計和面對每個環節。傳統的程式開發也是如此,在一個對象中,如果要使用另外的對象,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之後還要将對象銷毀(比如Connection等),對象始終會和其他的接口或類藕合起來。

l  借助于婚介(婚姻介紹所)找女朋友

那麼IoC是如何做的呢?有點像通過婚介找女朋友,在我和女朋友之間引入了一個第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚介提出一個清單,告訴它我想找個什麼樣的女朋友,比如長得像李嘉欣,身材像林熙雷,唱歌像周傑倫,速度像卡洛斯,技術像齊達内之類的,然後婚介就會按照我們的要求,提供一個mm,我們隻需要去和她談戀愛、結婚就行了。簡單明了,如果婚介給我們的人選不符合要求,我們就會抛出異常。整個過程不再由我自己控制,而是有婚介這樣一個類似容器的機構來控制。

(2)Spring所倡導的開發方式---由容器幫助我們管理對象的生命周期和關系

l  我們隻需要将對象在Spring中進行登記

Spring所倡導的開發方式就是如此,所有的類都會在Spring容器中登記,告訴Spring你是個什麼東西,你需要什麼東西,然後Spring會在系統運作到适當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。

l  所有的類的建立、銷毀都由Spring來控制

所有的類的建立、銷毀都由Spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是Spring。對于某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被Spring控制,是以這叫控制反轉。

深入了解依賴注入

(1)IoC的實作前提---借助于依賴注入

IoC的一個重點是在系統運作中,動态的向某個對象提供它所需要的其他對象。這一點是通過DI(Dependency Injection,依賴注入)來實作的。

比如對象A需要操作資料庫,以前我們總是要在A中自己編寫代碼來獲得一個Connection對象,有了Spring我們就隻需要告訴Spring,A中需要一個Connection,至于這個Connection怎麼構造,何時構造,A并不需要知道。

在系統運作時,Spring會在适當的時候制造一個Connection,然後像打針一樣,注射到A當中,這樣就完成了對各個對象之間關系的控制。A需要依賴Connection才能正常運作,而這個Connection是由Spring注入到A中的,依賴注入的名字就這麼來的。

(2)如何實作依賴注入----通過reflection來實作DI

那麼DI是如何實作的呢?Java 1.3之後一個重要特征是反射(reflection),它允許程式在運作的時候動态的生成對象、執行對象的方法、改變對象的屬性,Spring就是通過反射來實作注入的。

利用下面的代碼可以從配置檔案中獲得某個元件對象,并且動态地給該元件的message屬性指派。

Properties pro = new Properties();

pro.load(new FileInputStream("config.properties"));

String actionImplName = (String)pro.get(actionBeanName);

String actionMessageProperty = (String)pro.get(actionMessagePropertyName);

Object obj = Class.forName(actionImplName).newInstance();

//BeanUtils是Apache Commons BeanUtils提供的輔助類

BeanUtils.setProperty(obj,"message", actionMessageProperty);

return (Action)obj;

Spring IOC與工廠模式的對比

IOC(Inversion of Control),譯作反轉控制,其功能是将類之間的依賴轉移到外部的配置檔案中, 避免在調用類中寫死實作類,是以也被稱作依賴注入(Dependency Injection)。

在以往的開發中, 通常利用工廠模式(Factory)來解決此類問題----使外部調用類不需關心具體實作類,這樣非常适合在同一個事物類型具有多種不同實作的情況下使用。其實不管是工廠模式還是依賴注入,調用類與實作類不可能沒有任何依賴,工廠模式中工廠類通常根據參數來判斷該執行個體化哪個實作類,Spring IOC将需要執行個體的類在配置檔案檔案中配置。

使用Spring IOC能得到工廠模式同樣的效果,而且編碼更加簡潔。

(1)用工廠模式來實作的示例

    當我們在應用系統中的元件設計完全是基于接口定義時,一個關鍵問題便産生了-----我們的程式如何去加載接口的各個實作類。在傳統的解決方案種往往基于Factory模式來實作。

l  Product.java(代表某種産品類的接口,也就是我們所要建立的對象所應該具有的功能要求)

public interface Product

{

public void execute();

}

l  不同的産品類(也就是我們所要建立的各個對象)

public class ConcreteProductA implements Product   // ConcreteProductA.java

public void execute()

...

public class ConcreteProductB implements Product   // ConcreteProductB.java

l  Factory.java(工廠類,利用它來建立出不同類型的産品---客戶所需要的對象)

public class Factory

public Product CreateProduct(object param)

return ConstructObjects(param);

private Product ConstructObjects(object param)

...//根據不同的産品類型的需求來建立不同的産品對象

l  Client.java(調用類,也就是請求者類)

public class Client

public Client()

Product product = Factory.CreateProduct(paramA);  //執行個體化ConcreteProductA

Product product = Factory.CreateProduct(paramB);  //執行個體化ConcreteProductB

通過工廠模式,最終達到在ConstructObjects方法中設定執行個體化實作類的邏輯,這樣對于調用類來說,不直接執行個體化實作類(工廠模式中工廠類通常根據參數來判斷該執行個體化哪個實作類),縱然實作類發生變化,而調用代碼仍然可以不作修改,給維護與擴充帶來便利----系統中的其他元件需要擷取這個接口的實作,而無需事先獲知其具體的實作。

    但采用工廠模式來實作時,将會有如下三個主要的缺點:

l  除非重新編譯,否則無法對實作類進行替換。

必須重新編譯工廠類使得原本可以達成的易用性大大降低。在過去,Spring誕生之前,許多項目中,我們通過引入可配置化工廠類的形式,為這種基于接口的設計提供足夠的支援。這解決了執行個體化的問題,但是它為我們的項目開發帶來了額外的負擔,同時,它也沒有真正幫我們解決其餘兩個問題。

l  無法透明的為不同元件提供多個實作

這是我們在應用工廠模式時一個比較頭疼的問題,因為Factory類要求每個元件都必須遵從Factory類中定義的方法和結構特征。

當然我們可以在代碼的實作的形式上為Factory類中的ConstructObjects方法增加一個參數,通過該參數達到對接口實作的不同版本進行索引----這種實作方式的問題在于我們必須擔負很大的維護工作量,每個元件都必須使用一個不同的關鍵字。進而使得它必須以一種與衆不同的方式與其他元件的執行個體相區分。

l  無法簡單的進行切換執行個體産生的模型----單例或者原形

上面的代碼是實作了傳回多個執行個體的方式,如果我們需要保持了一個Singleton的執行個體,此時我們必須需要重新修改并編譯Factory類。

存在這個問題的核心是在于元件必須主動尋找接口的實作類,是以這個問題并不能通過傳統的工廠模式加以解決。

(2)用Spring IOC實作的示例 

l  SpringConfig.xml

<bean id="productA" class="ConcreteProductA" />

<bean id="productB" class="ConcreteProductB" />

l  InitSpring.java

public class InitSpring

AbstractApplicationContext wac = null;

private static InitSpring instance = new InitSpring();

private InitSpring()

public static void Init(AbstractApplicationContext wac)

instance.wac = wac;

public static Object getInstance(String objName)

return instance.wac.getBean(objName);

public static Object getInstance(Class objClass)

return getInstance(objClass.getName());

l  Client.java(調用類)

Product product = (Product)InitSpring.getObject("productA");//執行個體化ConcreteProductA

Product product = (Product)InitSpring.getObject("productB");//執行個體化ConcreteProductB

對比調用代碼,其中同樣也沒有寫死實作類,但比較工廠模式,少了Factory類而且采用配置檔案來決定各個産品的實作類,使用Spring IOC能得到工廠模式同樣的效果,而且編碼更加簡潔、靈活友善。

Spring對于基于接口設計的應用造成了極大的沖擊效應。因為Spring接過了将所有元件進行串聯組裝的重任,我們無需再糾纏于遍布各處的工廠類設計。

通過以上的這些通俗易懂的例子來解釋Spring中的核心思想。不知你了解了嗎?如果還是不太明白,不用擔心後面的章節講解會讓你徹底明白的。

本文轉自 weijie@java 51CTO部落格,原文連結:http://blog.51cto.com/weijie/66457,如需轉載請自行聯系原作者