{{{ #!html
Spring 學習
}}} [[PageOutline]] 引用 : [http://caterpillar.onlyfun.net/Gossip/SpringGossip/SpringGossip.html] = 理由 = 閱讀「iT人甘苦談─穩定的工作並非人生的全部」 最後一段描述:「對於掌握程式外包的品質,具有相當程度的信心。….比如開發一個以Java為主的專案.....會要求,團隊必須遵照Spring Framework的應用程式框架開發。..只要依照標準,基本上品質不會差到哪裡去。」 = spring = Spring 的核心是個輕量級(Lightweight)容器(Container),實現了IoC(Inversion of Control)模式的容器,基於此核心容器所建立的應用程式,可以達到程式元件的鬆散耦合(Loose coupling),讓程式元件可以進行測試(Testability),這些特性都使得整個應用程式可以在架構上與維護上都能得到相當程度的簡化。 * 輕量級(Lightweight) * 核心在檔案容量上只有不到 1MB 的大小 * 使用核心所需要的資源也是很小的 * 非侵入性(Nonintrusive)框架,它的目的之一,是讓應用程式不感受到框架的存在,減低應用程式從框架移植時的負擔。 * 容器(Container) * 管理物件的生命週期、物件的組態、相依注入等 * 控制物件在創建時是以 原型(Prototype) 或 單例(Singleton) 的方式來建立。 * IoC = Inversion of Control,控制反轉。 * 在Java開發過程中,IoC意謂著將你設計好的類別交給系統去控制,而不是在你的類別內部自己控制。 * Spring 的核心概念是IoC,更具體而易懂的名詞是依賴注入(Dependency Injection) * 不必自己在程式碼中維護物件的依賴關係,只需在組態檔中加以設定 除了這些特性之外,Spring 的目標是實現一個全方位的整合框架,在 Spring 框架下實現多個子框架的組合,這些子框架之間彼此可以獨立,也可以使用其它的框架方案加以替代,Spring 希望提供 one-stop shop 的框架整合方案。 * AOP 框架 * 支援AOP(Aspect-oriented programming) * 持久層 * JDBC、O/R Mapping工具(Hibernate、iBATIS)、事務處理等。 * Web 框架 * 提供 Web 框架的解決方案 * 也可以將自己所熟悉的 Web 框架與 Spring 整合 * 如 Struts、Webwork 等 對於一些服務,例如 JNDI、Mail、排程、遠程等,Spring 不直接提供實作,而是採取抽象層方式進行包裝,讓這些服務在使用時可以有一致的使用模式且更為方便。 = IoC 模式 = Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。 IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。 初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。 Dependency Inversion 簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。 舉個例子,例如下面這個程式: {{{ #!java #include .... void save() { .... saveToFloppy() } } }}} 由於save()程式依賴於依賴於saveToFloppy(),如果今天要更換低層的存儲模組為Usb碟,則這個程式沒有辦法重用,必須加以修改才行,低層模組的更動造成了高層模組也必須跟著更動,這不是一個好的設計方式,在設計上希望模組都依賴於模組的抽象,這樣才可以重用高層的業務設計。 如果以物件導向的方式來設計,Dependency Injection 的解釋變為程式不應依賴實作,而是依賴於抽象,實作必須依賴於抽象。來看看下面這個 Java 程式: {{{ #!java public class BusinessObject { private FloppyWriter writer = new FloppyWriter(); .... public void save() { ... writer.saveToFloppy(); } } }}} 在這個程式中,BusinessObject 的存檔依賴於實際的 FloppyWriter,如果今天想要將存檔改為存至 Usb 碟,則必須修改或繼承 BusinessObject 進行擴展,而無法直接使用BusinessObject。 如果透過介面的宣告,可以改進此一情況,例如: {{{ #!java 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: {{{ #!java public class FloppyWriter implement IDeviceWriter { public void saveToDevice() { .... // 實際儲存至Floppy的程式碼 } } public class UsbDiskWriter implement IDeviceWriter { public void saveToDevice() { .... // 實際儲存至UsbDisk的程式碼 } } }}} 從這個角度來看,Dependency Injection 的意思即是程式不依賴於實作,而是程式與實作都要依賴於抽象。 IoC 的 Control 是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,可以獲得元件的可重用性,在上面的 Java 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的 IDeviceWriter 介面上,使得BusinessObject、FloppyWriter、UsbDiskWriter 這幾個實現依賴於抽象的 IDeviceWriter 介面。 程式的業務邏輯部份應是可以重用的,不應受到所使用框架或容器的影響,因為可能轉移整個業務邏輯至其它的框架或容器,如果業務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。 IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:"Don't call me, I'll call you." 以程式的術語來說的話,就是「不要向容器要求您所需要的(物件)資源,容器會自動將這些物件給您!」。IoC 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。 == Dependency Injection == Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入 依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」 依賴注入在Martin Fowler的文章中談到了三種實現方式: * Interface injection (Type 1 IoC ) * Setter injection (Type 2 IoC ) * Constructor injection ( Type 3 IoC ) 上面的BusinessObject所實現的是Type 2 IoC,透過Setter注入依賴關係, == Type 2 IoC : Setter injection == BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面,複習一下 IoC 該節的例子(同上): {{{ #!java 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想要與UseDiskWriter物件發生依賴關係,可以這麼建立: {{{ #!java businessObject.setDeviceWriter(new UsbDiskWriter()); }}} 由於BusinessObject依賴於抽象介面,在需要建立依賴關係時,可以透過抽象介面注入依賴的實際物件。 == Type 3 IoC : Constructor injection == Type 3 IoC,則在是建構式上注入依賴關係,例如: {{{ #!java public class BusinessObject { private IDeviceWriter writer; public BusinessObject(IDeviceWriter writer) { this.writer = writer; } public void save() { .... writer.saveToDevice(); } } }}} Spring 鼓勵的是 Setter injection,但也允許您使用 Constructor injection,使用 Setter 或 Constructor 來注入依賴關係視您的需求而定,使用 Constructor 的好處之一是,您可以在建構物件的同時一併完成依賴關係的建立,然而如果要建立的物件關係很多,則會在建構式上留下一長串的參數,這時使用 Setter 會是個不錯的選擇,另一方面,Setter 可以有明確的名稱可以瞭解注入的物件會是什麼,像是setXXX()這樣的名稱會比記憶Constructor上某個參數位置代表某個物件來得好。 == Type 1 IoC : Interface injection == Type 1 IoC是Interface injection,使用Type 1 IoC時會要求實作介面,這個介面是為容器所用的,容器知道介面上所規定的方法,它可以呼叫實作介面的物件來完成依賴關係的注入,例如: {{{ #!java 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在執行時期根據組態檔的設定來為您建立物件之間的依賴關係,您不必特地撰寫一些Helper來自行建立這些物件之間的依賴關係,這不僅減少了大量的程式撰寫,也降低了物件之間的耦合程度。