Spring入門教程

Spring入門教程
                                     
1.Spring簡介
(1)Spring是什麼
Spring是輕量級的J2EE應用程序開源框架。它由Rod Johnson創建。它是爲了解決企業應用開發的複雜性而創建的。Spring使用基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅限於服務器端的開發。從簡單性、可測試性和鬆耦合的角度而言,任何Java應用都可以從Spring中受益。
Spring的核心是個輕量級容器(container),實現了IoC(Inversion of Control)模式的容器,Spring的目標是實現一個全方位的整合框架,在Spring框架下實現多個子框架的組合,這些子框架之間彼此可以獨立,也可以使用其它的框架方案加以替代,Spring希望提供one-stop shop的框架整合方案
Spring不會特別去提出一些子框架來與現有的OpenSource框架競爭,除非它覺得所提出的框架夠新夠好,例如Spring有自己的 MVC框架方案,因爲它覺得現有的MVC方案有很多可以改進的地方,但它不強迫您使用它提供的方案,您可以選用您所希望的框架來取代其子框架,例如您仍可以在Spring中整合您的Struts框架。
Spring的核心概念是IoC,IoC的抽象概念是「依賴關係的轉移」,像是「高層模塊不應該依賴低層模塊,而是模塊都必須依賴於抽象」是IoC的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是IoC的一種表現,「應用程序不應依賴於容器,而是容器服務於應用程序」也是IoC的一種表現。回想一下面向對象的設計原則:OCP原則和DIP原則。
Spring的核心即是個IoC/DI的容器,它可以幫程序設計人員完成組件(類別們)之間的依賴關係注入(連結),使得組件(類別們)之間的依賴達到最小,進而提高組件的重用性,Spring是個低侵入性(invasive)的框架,Spring中的組件並不會意識到它正置身於Spring中,這使得組件可以輕易的從框架中脫離,而幾乎不用任何的修改,反過來說,組件也可以簡單的方式加入至框架中,使得組件甚至框架的整合變得容易。
Spring最爲人重視的另一方面是支持AOP(Aspect-Oriented Programming),然而AOP框架只是Spring支持的一個子框架,說Spring框架是AOP框架並不是一件適當的描述,人們對於新奇的 AOP關注映射至Spring上,使得人們對於Spring的關注集中在它的AOP框架上,雖然有所誤解,但也突顯了Spring的另一個令人關注的特色。
 Spring也提供MVC Web框架的解決方案,但您也可以將自己所熟悉的MVC Web框架與Spring解合,像是Struts、Webwork等等,都可以與Spring整合而成爲適用於自己的解決方案。
 Spring也提供其它方面的整合,像是持久層的整合如JDBC、O/R Mapping工具(Hibernate、iBATIS)、事務處理等等,Spring作了對多方面整合的努力,故說Spring是個全方位的應用程序框架。
Spring框架由七個定義明確的模塊組成:
 
圖1  Spring框架概覽圖
總的來說,Spring是一個輕量級的控制反轉(IoC)和麪向切面(AOP)的容器框架。
     輕量??從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架可以在一個大小隻有1MB多的JAR文件裏發佈。並且Spring所需的處理開銷也是微不足道的。此外,Spring是非侵入式的:典型地,Spring應用中的對象不依賴於Spring的特定類。 
     控制反轉??Spring通過一種稱作控制反轉(IoC)的技術促進了鬆耦合。當應用了IoC,一個對象依賴的其它對象會通過被動的方式傳遞進來,而不是這個對象自己創建或者查找依賴對象。你可以認爲IoC與JNDI相反??不是對象從容器中查找依賴,而是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。
     面向切面??Spring提供了面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務()管理)進行內聚性的開發。應用對象只實現它們應該做的??完成業務邏輯??僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如日誌或事務支持。 
     容器??Spring包含並管理應用對象的配置和生命週期,在這個意義上它是一種容器,你可以配置你的每個bean如何被創建??基於一個可配置原型(prototype),你的bean可以創建一個單獨的實例或者每次需要時都生成一個新的實例??以及它們是如何相互關聯的。然而,Spring不應該被混同於傳統的重量級的EJB容器,它們經常是龐大與笨重的,難以使用。 
     框架??Spring可以將簡單的組件配置、組合成爲複雜的應用。在Spring中,應用對象被聲明式地組合,典型地是在一個XML文件裏。Spring也提供了很多基礎功能(事務管理、持久化框架集成等等),將應用邏輯的開發留給了你。 
所有Spring的這些特徵使你能夠編寫更乾淨、更可管理、並且更易於測試的代碼。它們也爲Spring中的各種模塊提供了基礎支持。
(2)Spring的歷史
Spring的基礎架構起源於2000年早期,它是Rod Johnson在一些成功的商業項目中構建的基礎設施。
在2002後期,Rod Johnson發佈了《Expert One-on-One J2EE Design and Development》一書,並隨書提供了一個初步的開發框架實現??interface21開發包,interface21就是書中闡述的思想的具體實現。後來,Rod Johnson 在interface21 開發包的基礎之上,進行了進一步的改造和擴充,使其發展爲一個更加開放、清晰、全面、高效的開發框架??Spring。
2003年2月Spring框架正式成爲一個開源項目,併發佈於SourceForge中。
(3)Spring的使命
     J2EE應該更加容易使用。
     面向對象的設計比任何實現技術(比如J2EE)都重要。
     面向接口編程,而不是針對類編程。Spring將使用接口的複雜度降低到零。(面向接口編程有哪些複雜度?)
     代碼應該易於測試。Spring框架會幫助你,使代碼的測試更加簡單。
     JavaBean提供了應用程序配置的最好方法。
     在Java中,已檢查異常(Checked exception)被過度使用。框架不應該迫使你捕獲不能恢復的異常。

2.控制反轉IoC
IoC全名Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。初看IoC,從字面上不容易瞭解其意義,我覺得要了解IoC,最好先從Dependency Inversion開始瞭解,也就是依賴關係的反轉。
Dependency Inversion在面向對象的設計原則之依賴倒置原則(DIP,Dependence Inversion Principle)中有着清楚的解?。
簡單的說,在模塊設計時,高層的抽象模塊通常是與業務相關的模塊,它應該具有重用性,而不依賴於低層的實現模塊,例如如果低層模塊原先是軟盤存取模式,而高層模塊是個存盤備份的需求,如果高層模塊直接叫用低層模塊的函式,則就對其產生了依賴關係。請看下面的例子:






void Copy(){
   int c;
   while ((c = ReadKeyboard()) != EOF)        
     WritePrinter(c);
}
這是僵化和不易改動的例子,爲什麼呢?很顯然,如果我還要將內容輸出到磁盤上(如下圖所示),那麼我們必須改動Copy的內容,並進行重新的測試和編譯。

 
改動後的程序如下所示:
enum OutputDevice {printer, disk}; 
void Copy(OutputDevice dev){
   int c;
   while((c = ReadKeyboard())!= EOF)           
      if(dev == printer)
         WritePrinter(c);
      else
         WriteDisk(c);
}
如果要繼續添加別的輸入或輸出方式,該程序還是無法重用,要對此程序進行修改才能繼續使用。
利用依賴倒置原則(DIP),可以解決這個問題。DIP原則,可以從2點來解讀:
     第1點:高層模塊不依賴底層模塊,兩者都依賴抽象
     第2點:抽象不應該依賴於細節,細節應該依賴於抽象
上面所講的例子如果用DIP原則,結果如下
 
class Reader {
  public: 
    virtual int read()=0;
};

class Writer {
  public: 
    virtual void write(int)=0;
};

void Copy(Reader& r, Writer& w){
   int c;
   while((c = r.read()) != EOF)
     w.write(c);
}
這樣一來,如果要添加新的輸入或輸出設備時只要改動相應的類(class Reader,Writer,利用多態來解決上面的問題)就可以了,而其它的程序都不用改動。這就是依賴倒置原則的基本內涵。
在軟件設計和構建中我們要遵循“高內聚、低偶合”的原則。那麼,依賴對於我們來說究竟是好事還是壞事呢?
首先應該明白的是,類之間如果是零偶合的狀態是不能夠構建應用程序的,只能構建類庫。
但是由於人類的理解力和可控制的範圍有限,大多數人難以理解和把握過於複雜的系統。把軟件系統劃分成多個模塊,可以有效控制模塊的複雜度,使每個模塊都易於理解和維護。但在這種情況下,模塊之間就必須以某種方式交換信息,也就是必然要發生某種耦合關係。如果某個模塊和其它模塊沒有任何關聯(哪怕只是潛在的或隱含的依賴關係),我們就幾乎可以斷定,該模塊不屬於此軟件系統,應該從系統中剔除。如果所有模塊之間都沒有任何耦合關係,其結果必然是:整個軟件不過是多個互不相干的系統的簡單堆積,對每個系統而言,所有功能還是要在一個模塊中實現,這等於沒有做任何模塊的分解。
因此,模塊之間必定會有這樣或那樣的依賴關係,永遠不要幻想消除所有依賴。但是,過強的耦合關係(如一個模塊的變化會造成一個或多個其他模塊也同時發生變化的依賴關係)會對軟件系統的質量造成很大的危害。特別是當需求發生變化時,代碼的維護成本將非常高。所以,我們必須想盡辦法來控制和消解不必要的耦合,特別是那種會導致其它模塊發生不可控變化的依賴關係。依賴倒置、控制反轉就是人們在和依賴關係進行艱苦卓絕的鬥爭過程中不斷產生和發展起來的。
我們下面來繼續一步一步的說明這個問題。
看下面的程序:
#include <floppy.h>
....
void save() {
        ....
        saveToFloppy()
    }
}
由於save()程序依賴於saveToFloppy(),如果今天要更換低層的存儲模塊爲Usb碟,則這個程序沒有辦法重用,必須加以修改才行,低層模塊的更動造成了高層模塊也必須跟着更動,這不是一個好的設計方式,我們希望模塊都依賴於模塊的抽象,這樣纔可以重用高層的業務設計。
如果以面向對象的方式來設計,依賴倒置(Dependency Inversion)的解釋變爲程序不應依賴實現,而是依賴於抽象,實現必須依賴於抽象。我們來看看下面這個Java程序:
BusinessObject.java
public class BusinessObject {
    private FloppyWriter writer = new FloppyWriter();
    ....
   
    public void save() {
        ...
        writer.saveToFloppy();

    }
}

public class FloppyWriter {
    
    ....  //相應的寫盤的代碼
   
}
在這個程序中,BusinessObject的存盤依賴於實際的FloppyWriter,如果今天我們想要將存盤改爲存至Usb碟,我們必須修改或繼承BusinessObject進行擴展,而無法直接使用BusinessObject。
浩劫只是剛剛開始,這時,你一定和我一樣在期待着救世主的早日降臨??接口(Interface)。你一定會說,面向對象的設計原則已經告訴我們了啊,“要針對接口編程”,你爲什麼不用呢?好,我們採用這個原則進行編程:
什麼是接口?
     接口定義了行爲的協議,這些行爲在繼承接口的類中實現。
     接口定義了很多方法,但是沒有實現它們。類履行接口協議並實現所有定義在接口中的方法。
     接口是一種只有聲明沒有實現的特殊類。
使用接口的優點:
     Client不必知道其使用對象的具體所屬類。
     一個對象可以很容易地被(實現了相同接口的)的另一個對象所替換。
     對象間的連接不必硬綁定(hardwire)到一個具體類的對象上,因此增加了靈活性。
     鬆散藕合(loosens coupling)。
     增加了重用的可能性。
通過面向接口編程,可以改?此一情?,例如:
public interface IDeviceWriter {
    public void saveToDevice();
}

public class BusinessObject {
    private IDeviceWriter writer;

    public void setDeviceWriter(IDeviceWriter writer) {
        this.writer = writer;
    }

    public void save() {
        ....
        writer.saveToDevice();
    }
}
這樣一來,BusinessObject就是可重用的,如果今天我有存儲至Floppy或Usb碟的需求,我只要實現IDeviceWriter即可,而不用修改BusinessObject:
public class FloppyWriter implement IDeviceWriter {
    public void saveToDevice() {
        ....
        // 實際儲存至Floppy的程序代碼
    }
}

public class UsbDiskWriter implement IDeviceWriter {
    public void saveToDevice() {
        ....
        // 實際儲存至UsbDisk的程序代碼
    }
}
從這個角度來看,Dependency Inversion的意思即是程序不依賴於實現,而是程序與實現都要依賴於抽象。
哦,這樣一來,一切都OK了嗎?還沒有沒有問題呢,你可能會有疑問,我要根據不同的情況來使用軟盤、USB碟或者其它的存儲設備,怎麼辦呢?在程序包中,BusinessObject不是還是和FloppyWriter或者UsbDiskWriter綁定嗎,如果系統發佈後,要將FloppyWriter替換爲UsbDiskWriter不是還要去修改IDeviceWriter的實現嗎?修改就意味着可能會帶來錯誤,就要帶來修改代碼、進行測試、編譯、維護等的工作量。還有更好的方法嗎?IoC/DI就是解決之道。
IoC的Control是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,我們要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,我們可以獲得組件的可重用性,在上面的Java程序中,整個控制權從實際的 FloppyWriter轉移至抽象的IDeviceWriter接口上,使得BusinessObject、FloppyWriter、 UsbDiskWriter這幾個實現依賴於抽象的IDeviceWriter接口。
 使用IoC,對象是被動的接受依賴類,而不是自己主動的去找。容器在實例化的時候主動將它的依賴類注入給它。可以這樣理解:控制反轉將類的主動權轉移到接口上,依賴注入通過xml配置文件在類實例化時將其依賴類注入。
 使用IoC,對象是被動的接受依賴類,而不是自己主動的去找。容器在實例化的時候主動將它的依賴類注入給它。可以這樣理解:控制反轉將類的主動權轉移到接口上,依賴注入通過xml配置文件在類實例化時將其依賴類注入。
“控制反轉(Inversion of Control)與依賴倒置原則(Dependency Inversion Principle)是一個同義原則。雖然“依賴倒置”和“控制反轉”在設計層面上都是消解模塊耦合的有效方法,也都是試圖令具體的、易變的模塊依賴於抽象的、穩定的模塊的基本原則,但二者在使用語境和關注點上存在差異:“依賴倒置”強調的是對於傳統的、源於面向過程設計思想的層次概念的“倒置”,而“控制反轉”強調的是對程序流程控制權的反轉。“依賴倒置”的使用範圍更爲寬泛一些。
IoC在容器的角度,可以用這麼一句好萊塢名言來代表(著名的好萊塢原則):"Don't call me, I'll call you."(不要打電話給我們,我們會通知你的)。好萊塢的演員門(包括大牌)都會在好萊塢進行登記,他們不能夠直接打電話給製片人或者導演要求演出摸個角色或者參加某個片子的演出,而是由好萊塢根據需要去通知他們(前提是他們已經在好萊塢做過登記)。在這裏好萊塢就相當於容器。
 以程序的術語來說的話,就是「不要向容器要求您所需要的(對象)資源,容器會自動將這些對象給您!」。IoC要求的是容器不侵入應用程序本身,應用程序本身提供好接口,容器可以透過這些接口將所需的資源注至程序中,應用程序不向容器主動要求資源,故而不會依賴於容器的組件,應用程序本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。

3.依?注入DI
IoC模式基本上是一個高層的概念,在Martin Fowler的Inversion of Control Containers and the Dependency Injection pattern中談到,實現IoC有兩種方式:Dependency Injection與Service Locator。
Spring所採用的是Dependency Injection來實現IoC(多數容器都是採取這種方式的),中文翻譯爲依賴注入,依賴注入的意義是:保留抽象接口,讓組件依賴於抽象接口,當組件要與其它實際的對象發生依賴關係時,藉過抽象接口來注入依賴的實際對象。
回鍋頭來再仔細研讀一下我們在上面給出的例子:
public interface IDeviceWriter {
    public void saveToDevice();
}

public class BusinessObject {
    private IDeviceWriter writer;

    public void setDeviceWriter(IDeviceWriter writer) {
        this.writer = writer;
    }

    public void save() {
        ....
        writer.saveToDevice();
    }
}

public class FloppyWriter implement IDeviceWriter {
    public void saveToDevice() {
        ....
        // ???存至Floppy的程式?
    }
}

public class UsbDiskWriter implement IDeviceWriter {
    public void saveToDevice() {
        ....
        // ???存至UsbDisk的程式?
    }
}
爲了讓BusinessObject獲得重用性,我們不讓BusinessObject依賴於實際的FloppyWriter,而是依賴於抽象的接口。
在此代碼中,首先IDeviceWriter的變量writer可以接收任何IDeviceWriter的實例,另外,FloppyWriter或UsbDiskWrite的實例不是通過BusinessObject來獲得,而是通過setter(也可以用構造器)來由外部傳給它。這似乎跟我們往常的代碼沒什麼不同(回想一下Javabean的setter/getter),但這已經是一個良好的設計。現在的關鍵問題是:FloppyWriter或UsbDiskWrite的實例如何從外部注入給BusinessObject呢?這就要通過xml來實現了(相當於演員們在好萊塢登記)。
Spring的配置文件applicationContext.xml代碼片斷如下:
<beans>
    <bean id = "FloppyWriter" class = "iocfirst.business.write" />
    <bean
        id = "BusinessObject"
        class = "iocfirst.business.BusinessObject"
    />
        <property name = " FloppyWriter">
            <ref bean = " FloppyWriter " />
        </property>
    </bean>
</beans>
如果什麼時候想將UsbDiskWrite注入,則修改applicationContext.xm即可。
單有了這個xml文件還不夠,加載該xml文件呢?spring提供了現成的API,在加載上面的xml的時候,就進行了如下工作:實例化FloppyWriter或UsbDiskWrite類,實例化BusinessObject類,並將FloppyWriter或UsbDiskWrite的實例作爲參數賦給了BusinessObject實例的setDeviceWriter方法。
BusinessObject依賴於抽象接口,在需要建立依賴關係時,我們就是通過抽象接口注入依賴的實際對象。
依賴注入在Martin Fowler的文章中談到了三種實現方式:interface injection、setter injection與constructor injection。並分別稱其爲type 1 IoC、type 2 IoC與type 3 IoC。
     Type1-接口注入(Interface Injection)
它是在一個接口中定義需要注入的信息,並通過接口完成注入。Apache Avalon是一個較爲典型的Type1型IOC容器,WebWork框架的IoC容器也是Type1型。
     Type2-設值方法注入(Setter Injection)
在各種類型的依賴注入模式中,設值注入模式在實際開發中得到了最廣泛的應用(其中很大一部分得力於Spring框架的影響)。
基於設置模式的依賴注入機制更加直觀、也更加自然。上面的BusinessObject所實現的是type 2 IoC,透過setter注入依賴關係。
     Type3-構造子注入(Constructor Injection)
構造子注入,即通過構造函數完成依賴關係的設定。
目前我們只要瞭解到有這個3種方式就可以了,具體情況將在以後的章節中進行介紹。

4.開始spring之旅
任何需要交給spring管理的對象,都必須在配置文件中註冊,這個過程被稱爲wiring,下面做一個最簡單的演示,
第一步:下載Spring,Spring官方網站是http://www.springframework.org/我們這裏下載的是2.0.1版,spring-framework-2.0.1-with-dependencies.zip是該版本的壓縮包,參見圖2。
 
                  圖 2
將壓縮包的內容解壓到一個目錄中,D:/spring-framework-2.0.1是我這裏使用的存放目錄。
第二步:使用Eclipse的【new project】嚮導,新建一個名爲spring-demo的工程。參見圖3。
 
                           圖3

第三步:新建一個名爲HelloTalker的類,package選擇com.spring.demo,如“圖4”所示。
 
                圖 4
第四步:把所需要使用的Spring項目jar的文件加入到當前工程的Libraries中,本例中只使用Spring的簡單IOC功能,只需要spring-beans.jar、spring-core.jar以及commons-logging.jar三個包即可。
右鍵點擊工程名稱,查看工程屬性,選擇工程屬性中的【Java Builder Path】,然後選擇【Libraries】選項,通過點擊【add external jar】按鈕來把外部的jar文件添加到工程項目中。參見圖5所示:
 
                  圖 5
第五步,錄入HelloTalker.java的完整源代碼。其中HelloTalker這個類有一個msg屬性,爲該屬性編寫set方法,該方法必須嚴格遵守javabean的命名規則即有一個setMsg方法用於設置msg屬性的值。另外,我們直接在HelloTalker的main方法中使用Spring的Bean工廠來從配置文件中加載一個名爲helloBean,類型爲com.spring.demo.HelloTalker的Bean。然後調用這個Bean的sayHello方法。全部源代碼如下所示:
package com.spring.demo;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class HelloTalker {

    /**
     * @param args
     */
    private String  msg;
    public void setMsg(String msg)
    {
        this.msg=msg;
    }
    public void sayHello()
    {
        System.out.print(msg);
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Resource res=new ClassPathResource("com/spring/demo/springConfig.xml");
        BeanFactory factory=new XmlBeanFactory(res);
        HelloTalker hello=(HelloTalker)factory.getBean("helloBean");
        hello.sayHello();
}
}

第六步,任何需要交給spring管理的對象,都必須在配置文件中註冊,這個過程被稱爲wiring。因此,在與HelloTalker.java同級的目錄下,建一個名爲springConfig.xml(文件名可以任意定)的Spring配置文件,在這個Bean配置文件中定義helloBean,並通過依賴注入設置helloBean的msg屬性值,其內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
   <bean id="helloBean" class="com.spring.demo.HelloTalker">
       <property name="msg" value="Hello World的Spring示例!"/>
   </bean>   
</beans>
參見圖6:
 
圖6,工程完成後的情況
第七步:運行程序,在HelloTalker.java上點右鍵,選擇【Run As】下面的【Java Application】。即可運行這個使用了Spring的程序,該程序將輸出helloBean中的msg屬性的值:“Hwllo World的Spring示例!”,如圖7所示。
 
圖7 程序運行的結果
至此爲止,我們就完成了一個最簡單的Spring應用實踐。更多的內容將在後面的部分逐步給出。

4.注入方式
前面已經提到依賴注入方式由種,Spring鼓勵的是setter injection,但也允許您使用constructor injection,使用setter或constructor來注入依賴關係視您的需求而定,使用constructor的好處之一是,您可以在建構對象的同時一併完成依賴關係的建立,然而如果要建立的對象關係很多,則會在建構函式上留下一長串的參數,這時使用setter會是個不錯的選擇,另一方面, setter可以有明確的名稱可以瞭解注入的對象會是什麼,像是setXXX()這樣的名稱會比記憶constructor上某個參數位置代表某個對象來得好。
前面講的例子採用的是setter injection。Type 1 IoC是interface injection,使用type 1 IoC時會要求實現接口,這個接口是爲容器所用的,容器知道接口上所規定的方法,它可以呼叫實現接口的對象來完成依賴關係的注入,例如:
public interface IDependencyInjection {
    public void createDependency(Map dependObjects);
}

public class BusinessObject implement IDependencyInjection {
    private Map dependObjects;

    public void createDependency(Map dependObjects) {
        this.dependObject = dependObjects;
        // 在這邊實現與BusinessObject的依賴關係
        ......
    }

    public void save() {
        ....
        writer.saveToDevice();
    }
}

如果要完成依賴關係注入的對象,必須實現IDependencyInjection接口,並交由容器管理,容器會呼叫被管理對象的createDependency()方法來完成依賴關係的建立。
在上面的例子中,type 1 IoC要求BusinessObject實現特定的接口,這就使得BusinessObject依賴於容器,如果日後BusinessObject要脫離目前這個容器,就必須修改程序,想想在更復雜的依賴關係中產生更多複雜的接口,組件與容器(框架)的依賴會更加複雜,最後使得組件無法從容器中脫離。
所以type 1 IoC具有強的侵入性,使用它來實現依賴注入會使得組件相依於容器(框架),降低組件的重用性。

Spring的核心是個IoC容器,您可以用setter或constructor的方式來實現您的業務對象,至於對象與對象之間的關係建立,則透過組態設定,讓Spring在執行時期根據組態檔的設定來爲您建立對象之間的依賴關係。
那麼,如何使用construtor injection呢?首先看看HelloBean:
HelloBean.java
package onlyfun.caterpillar;

public class HelloBean {
    private String helloWord = "hello";
    private String user = "NoBody";
   
    public HelloBean(String helloWord, String user) {
        this.helloWord = helloWord;
        this.user = user;
    }   
   
    public String sayHelloToUser() {
        return helloWord + "!" + user + "!";
    }
}
爲了突顯構造函式,我們這個HelloBean設計的簡陋,只提供了構造函式與必要的sayHelloToUser(),我們來看看bean的定義檔案:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="helloBean" class="onlyfun.caterpillar.HelloBean">
        <constructor-arg index="0"><value>Greeting</value></constructor-arg>
        <constructor-arg index="1"><value>Justin</value></constructor-arg>
    </bean>
</beans>
在bean的定義檔案中,我們使用<constructor-arg>來表示我們將使用constructor injection,由於使用constructor injection時並不如setter injection時擁有setXXX()這樣易懂的名稱,所以我們必須指定參數的位置索引,index屬性就是用於指定我們的對象將注入至構造函式中的哪一個參數,索引值從0開始,符合Java索引的慣例。
來看看測試程序:
Test.java
package onlyfun.caterpillar;

import java.io.*;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;

public class Test {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream("bean.xml");
        BeanFactory factory = new XmlBeanFactory(is);
       
        HelloBean hello = (HelloBean) factory.getBean("helloBean");
        System.out.println(hello.sayHelloToUser());
    }
}

幾種依賴注入模式的特點如下:
接口注入模式因爲歷史較爲悠久,在很多容器中都已經得到應用。但由於其在靈活性、易用性上不如
其他兩種注入模式,因而在IOC的專題世界內並不被看好。
Type2和Type3型的依賴注入實現則是目前主流的IOC實現模式。這兩種實現方式各有特點,也各具優勢。
Type2 設值注入的優勢
1. 對於習慣了傳統JavaBean開發的程序員而言,通過setter方法設定依賴關係顯得更加直觀,更加自然。
2. 如果依賴關係(或繼承關係)較爲複雜,那麼Type3模式的構造函數也會相當龐大(我們需要在構造函數中設定所有依賴關係),此時Type2模式往往更爲簡潔。
3. 對於某些第三方類庫而言,可能要求我們的組件必須提供一個默認的構造函數(如Struts中的Action),此時Type3類型的依賴注入機制就體現出其侷限性,難以完成我們期望的功能。
Type3 構造子注入的優勢:
1. “在構造期即創建一個完整、合法的對象”,對於這條Java設計原則,Type3無疑是最好的響應者。
2. 避免了繁瑣的setter方法的編寫,所有依賴關係均在構造函數中設定,依賴關係集中呈現,更加易讀。
3. 由於沒有setter方法,依賴關係在構造時由容器一次性設定,因此組件在被創建之後即處於相對“不變”的穩定狀態,無需擔心上層代碼在調用過程中執行setter方法對組件依賴關係產生破壞,特別是對於Singleton模式的組件而言,這可能對整個系統產生重大的影響。
4. 同樣,由於關聯關係僅在構造函數中表達,只有組件創建者需要關心組件內部的依賴關係。對調用者而言,組件中的依賴關係處於黑盒之中。對上層屏蔽不必要的信息,也爲系統的層次清晰性提供了保證。
5. 通過構造子注入,意味着我們可以在構造函數中決定依賴關係的注入順序,對於一個大量依賴外部服務的組件而言,依賴關係的獲得順序可能非常重要,比如某個依賴關係注入的先決條件是組件的UserDao及相關資源已經被設定。
可見,Type3和Type2模式各有千秋,而Spring、PicoContainer都對Type3和Type2類型的依賴注入機制提供了良好支持。這也就爲我們提供了更多的選擇餘地。理論上,以Type3類型爲主,輔之以Type2類型機制作爲補充,可以達到最好的依賴注入效果,不過對於基於Spring Framework開發的應用而言,Type2使用更加廣泛。

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