Spring 之 IOC/DI

IoC(Inversion of Control,控制反轉)也稱爲依賴注入(Dependency Injection),作爲Spring的一個核心思想,是一種設計對象之間依賴關係的原則及其相關技術。IOC的思想最核心的地方在於,資源不由使用資源的雙方管理,而由不使用資源的第三方管理。這可以帶來很多好處:第一,資源集中管理,實現資源的可配置和易管理。第二,降低了使用資源雙方的依賴程度,也就是我們說的耦合度。


IoC理論的背景

在採用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,所有的對象通過彼此的合作,最終實現系統的業務邏輯。

軟件系統中耦合的對象 對象之間複雜的依賴關係
 傳統應用程序示意圖 有IoC/DI容器後程序結構示意圖
齒輪組中齒輪之間的齧合關係,與軟件系統中對象之間的耦合關係非常相似。對象之間的耦合關係是無法避免的,也是必要的,這是協同工作的基礎。 伴隨着工業級應用的規模越來越龐大,對象之間的依賴關係也越來越複雜,經常會出現對象之間的多重依賴性關係,因此,架構師和設計師對於系統的分析和設計,將面臨更大的挑戰。對象之間耦合度過高的系統,必然會出現牽一髮而動全身的情形。

耦合關係不僅會出現在對象與對象之間,也會出現在軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何降低系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。爲了解決對象之間的耦合度過高的問題,軟件專家Michael Mattson提出了IOC理論,用來實現對象之間的“解耦”,目前這個理論已經被成功地應用到實踐當中,很多的J2EE項目均採用了IOC框架產品spring。

Iteye 開濤對Ioc的講解

首先要分享的是Iteye的開濤這位技術牛人對Spring框架的IOC的理解,以下內容全部來自原文。原文地址

IoC是什麼

  • 誰控制誰,控制什麼
    傳統Java SE程序設計,我們直接在對象內部通過new進行創建對象,是程序主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
  • 爲何是反轉,哪些方面反轉了
    有反轉就有正轉,傳統應用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及注入依賴對象;爲何是反轉?因爲由容器幫我們查找及注入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。
傳統應用程序示意圖 有IoC/DI容器後程序結構示意圖
 傳統應用程序示意圖 有IoC/DI容器後程序結構示意圖

IoC能做什麼

IoC不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把創建和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程序的整個體系結構變得非常靈活。
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程序原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來創建並注入它所需要的資源了。
IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對象找相應的依賴對象並注入,而不是由對象主動去找。

IoC和DI

DI—Dependency Injection,即“依賴注入”:是組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係注入到組件之中。依賴注入的目的並非爲軟件系統帶來更多功能,而是爲了提升組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

理解DI的關鍵是:“誰依賴誰,爲什麼需要依賴,誰注入誰,注入了什麼”,那我們來深入分析一下:

  • 誰依賴於誰:當然是應用程序依賴於IoC容器;
  • 爲什麼需要依賴:應用程序需要IoC容器來提供對象需要的外部資源;
  • 誰注入誰:很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象;
  • 注入了什麼:就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)。

IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解爲容器控制對象這一個層面,很難讓人想到誰來維護對象關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入對象依賴IoC容器配置依賴對象”。

個人理解

IoC

在封建主義社會。當一個家庭(調用者)需要糧食(被調用者)時只有自己去耕種。
對應JAVA中:調用類需要被調用類的實例,只有通過關鍵字New去創建。該模式下代碼高度耦合。會出現擴展性差、各模塊職責不清晰等情況。

在資本主義社會。糧食(被調用者)可不由家庭(調用者)去生產,而轉爲農場(第三方容器)統一耕種,家庭的人不需要關心糧食的耕種細節。當需要糧食的時候通過地址去農場購買即可。

在社會主義社會。當家庭(調用者)想要糧食(被調用者),無需主動去找農場(第三方容器),只需通過電話,農場主動派人將糧食送到家庭中。
對應JAVA中:在調用類無需引入工廠類地址,更不用關心調用類的實例化,只需spring的注入即可。該模式下調用者不關心被調者的具體實現,更不用主動定位工廠,實現了最大程度的解耦。

DI

電腦主機和USB接口來實現一個任務:從外部USB設備讀取一個文件。電腦主機讀取文件的時候,它一點也不會關心USB接口上連接的是什麼外部設備,而且它確實也無須知道。它的任務就是讀取USB接口,掛接的外部設備只要符合USB接口標準即可。所以,如果我給電腦主機連接上一個U盤,那麼主機就從U盤上讀取文件;如果我給電腦主機連接上一個外置硬盤,那麼電腦主機就從外置硬盤上讀取文件。掛接外部設備的權力由我作主,即控制權歸我,至於USB接口掛接的是什麼設備,電腦主機是決定不了,它只能被動的接受。當電腦主機讀取文件的時候,我就把它所要依賴的外部設備,幫他掛接上。整個外部設備註入的過程和一個被依賴的對象在系統運行時被注入另外一個對象內部的過程完全一樣。
對應JAVA中:對象A依賴於對象B,當對象 A需要用到對象B的時候,IOC容器就會立即創建一個對象B送給對象A。IOC容器就是一個對象製造工廠,你需要什麼,它會給你送去,你直接使用就行了,而再也不用去關心你所用的東西是如何製成的,也不用關心最後是怎麼被銷燬的,這一切全部由IOC容器包辦。

注入方式

構造器注入

就是容器實例化Bean時注入那些依賴,通過在在Bean定義中指定構造器參數進行注入依賴,包括實例工廠方法參數注入依賴,但靜態工廠方法參數不允許注入依賴;
 傳統應用程序示意圖

注入方式 注入配置方式示例
參數索引注入 參數索引注入
參數類型注入 參數類型注入
參數名注入 參數名注入

測試構造器類HelloImpl3.java,該類只有一個包含兩個參數的構造器:

package cn.javass.spring.chapter3.helloworld;  
public class HelloImpl3 implements HelloApi {  
    private String message;  
    private int index;  
    public HelloImpl3(String message, int index) {  
        this.message = message;  
        this.index = index;  
    }  
    @Override  
    public void sayHello() {  
        System.out.println(index + ":" + message);  
    }  
}  

相關配置:

<bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">  
    <constructor-arg index="0" value="Hello World!"/>  
    <constructor-arg index="1" value="1"/>  
</bean>  
<!-- 通過構造器參數類型方式依賴注入 -->  
<bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">  
   <constructor-arg type="java.lang.String" value="Hello World!"/>  
   <constructor-arg type="int" value="2"/>  
</bean>  
<!-- 通過構造器參數名稱方式依賴注入 -->  
<bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">  
   <constructor-arg name="message" value="Hello World!"/>  
   <constructor-arg name="index" value="3"/>  
</bean> 

setter注入

setter注入,是通過在通過構造器、靜態工廠或實例工廠實例好Bean後,通過調用Bean類的setter方法進行注入依賴
setter注入方式

注入方式 注入配置方式示例
setter注入方式 setter注入方式

(1)準備測試類HelloImpl4,需要兩個setter方法“setMessage”和“setIndex”:

package cn.javass.spring.chapter3;  
import cn.javass.spring.chapter2.helloworld.HelloApi;  
public class HelloImpl4 implements HelloApi {  
    private String message;  
    private int index;  
//setter方法  
    public void setMessage(String message) {  
        this.message = message;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
    @Override  
    public void sayHello() {  
        System.out.println(index + ":" + message);  
    }  
}  

配置Bean定義,具體配置文件(resources/chapter3/setterDependencyInject.xml)片段如下:

<!-- 通過setter方式進行依賴注入 -->  
    <bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4">  
        <property name="message" value="Hello World!"/>  
        <property name="index">  
            <value>1</value>  
        </property>  
    </bean>  

(3)該寫測試進行測試一下是否滿足能工作了,其實測試代碼一點沒變,變的是配置:

@Test  
public void testSetterDependencyInject() {  
    BeanFactory beanFactory =  
new ClassPathXmlApplicationContext("chapter3/setterDependencyInject.xml");  
   HelloApi bean = beanFactory.getBean("bean", HelloApi.class);  
    bean.sayHello();  
}  

兩種注入方式的比較

在過去的開發過程中,這兩種注入方式都是非常常用的。Spring也同時支持兩種依賴注入方式:設值注入和構造注入。 這兩種依賴注入的方式,並沒有絕對的好壞,只是適應的場景有所不同。
建議:採用以設值注入爲主,構造注入爲輔的注入策略。對於依賴關係無需變化的注入,儘量採用構造注入;而其他的依賴關係的注入,則考慮採用設值注入。

設值注入有如下優點

  • 設值注入需要該Bean包含這些屬性的setter方法;
  • 與傳統的JavaBean的寫法更相似,程序開發人員更容易理解、接收。通過setter方法設定依賴關係顯得更加只管;
  • 對於複雜的依賴關係,如果採用構造注入,會導致構造器國語臃腫,難以閱讀。Spring在創建Bean實例時,需要同時實例化器依賴的全部實例,因而導致性能下降。而使用設值注入,則能避免這些問題;
  • 尤其是在某些屬性可選的情況況下,多參數的構造器顯得更加笨重;

構造注入有以下優勢

  • 構造注入需要該Bean包含帶有這些屬性的構造器;
  • 構造注入可以在構造器中決定依賴關係的注入順序,優先依賴的優先注入。例如,組件中其他依賴關係的注入,常常要依賴於DataSrouce的注入。採用構造注入,可以在代碼中清晰的決定注入順序;
  • 對於依賴關係無需變化的Bean,構造注入更有用處。因爲沒有Setter方法,所有的依賴關係全部在構造器內設定。因此,無需擔心後續的代碼對依賴關係產生破壞;
  • 依賴關係只能在構造器中設定,則只有組件的創建者才能改變組件的依賴關係。對組件的調用者而言,組件內部的依賴關係完全透明,更符合高內聚的原則;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章