spring 2.0 技術手冊第一章 - 認識 spring

17
認識 Spring Spring Lightweight Container IoC Inversion of Control No intrusive AOP Aspect-oriented programming Persistence Transaction MVC Web API Application Interface Application framework Struts JSF Hibernate Spring Spring IoC AOP 1

Upload: justin-lin

Post on 16-Jul-2015

5.643 views

Category:

Technology


8 download

TRANSCRIPT

Page 1: Spring 2.0 技術手冊第一章 - 認識 Spring

認識 Spring

Spring 的核心是個輕量級(Lightweight)的容器(Container),它實現 IoC(Inversion of Control)容器、非侵入性(No intrusive)的框架,並提供 AOP(Aspect-oriented programming)的實現方式,提供對持久層(Persistence)、交易(Transaction)的支援,提供MVC Web 框架的實現,並對於一些常用的企業服務 API(Application

Interface)提供一致的模型封裝,是一個全方位的應用程式框架(Application framework),除此之外,對於現存的各種框架(Struts、JSF、Hibernate 等),Spring 也提供了與它們相整合的方案。 對於打算入門 Spring 的初學者來說,面對輕量級、非侵入性、容器、IoC、AOP 等術語往往不知所措,在這個章節中,將針對這些術語作

1

Page 2: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�2

一些介紹,以作為往後各個章節中,介紹 Spring 對這些術語概念實現時的基礎。

Page 3: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�3

1.1 術語介紹

Spring 的目標之一,是作為一個全方位的應用程式框架(Application

framework),由於在今日,應用程式於各個領域的應用越來越複雜,因而各個領域在設計與實作上的概念與術語也越來越多,作為一個全方面的應用程式框架(Framework),在使用 Spring時就得接觸到像是輕量級、容器、非侵入性(No intrusive)、IoC(Inversion of Control)、AOP(Aspect-

oriented programming)等術語,對於入門 Spring的初學者而言,以下的說明可以讓您對這些術語與概念有個基本的認識。

� 輕量級(Lightweight) 輕量級的形容是相對於一些重量級的容器(如 EJB 容器)來說的,Spring的核心在檔案容量上只有不到 1MB 的大小,而使用 Spring 核心所需要的資源負擔也是很小的,您甚至可以在小型設備中使用 Spring。

� 非侵入性(No intrusive) 框架原來的用意是提供一個架構的實現,讓開發人員可以基於框架的基礎上,快速的開發出依循架構下所需的應用程式,然而有些框架一旦被使用,應用程式就與框架發生了依賴,例如大量使用了框架的 API,或直接繼承API 的某些類別等,都會使應用程式組件與框架發生依賴,而無法從框架中獨立出來,更別說當中的組件可以直接重用於另一個應用程式之中。

Spring 的目標之一是實現一個非侵入性(No intrusive)框架,希望讓應用程式幾乎感受不到框架的存在,減低應用程式從框架移植時的負擔,進一步增加應用程式組件的可重用性(Reusability)。簡單的說,使用 Spring的話,應用程式中某些組件,可以直接拿到另一個應用程式或框架之中直接使用。

Page 4: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�4

� 容器(Container) 容器可以管理物件的生成、資源取得、銷毀等生命週期,甚至完成物件與物件之間的依賴關係。Spring 提供容器功能,您可以使用一個組態設定檔案(通常是 XML),在當中定義物件名稱、如何產生(Prototype 方式或Singleton 方式)、哪個物件產生之後必須設定成為某個物件的屬性等,在啟動容器之後,所有的物件都可以直接取用,不用撰寫任何一行程式碼來產生物件,或是建立物件與物件之間的依賴關係。 從 Java 的角度具體來說,容器就是一個 Java 所撰寫的程式,原先必須自行撰寫程式以管理物件關係,現在容器都會自動幫您作好。

� Inversion of Control 與 Dependency Injection

Spring 最重要的核心概念是 Inversion of Control,中文常翻作「控制反轉」,簡稱 IoC。在 Spring 中,「依賴關係的轉移」、「依賴於抽象而非實作」是重要的觀念,從物件的角度來說,可以避免物件之間的耦合,從容器的角度來說,可以避免應用程式依賴於容器的功能,造成應用程式從容器脫離的困難。

Spring 另一個重要核心概念為 Dependency Injection,中文常翻作「依賴注入」,簡稱 DI。使用 Spring,不必自己在程式碼中維護物件的依賴關係,只需在組態檔中加以設定,Spring 核心容器會自動根據組態,將依賴注入指定的物件。 在下一節中,還會詳細介紹 Inversion of Control 與 Dependency Injection的概念。

� AOP(Aspect-oriented programming)

Spring 最為人重視的功能之一是支援 AOP(Aspect-oriented programming)的實現,然而 AOP 框架只是 Spring 支援的一個子框架,說 Spring 框架是

AOP 框架並不是一件適當的描述,人們對於 AOP 的關注反映至 Spring 上,

Page 5: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1��

使得人們對於 Spring 的關注集中在它的 AOP 框架上,雖然有所誤解,但也突顯了 Spring 的另一個令人關注的特色。 舉個實際的例子來說明 AOP 的一個應用,假設應用程式有個日誌(Logging)的需求,您可以無須修改任何一行程式碼,就將這個需求加入至原先的應用程式中,而若您願意,也可以在不修改任何程式的情況下,將這個日誌功能移除。

Spring 的 IoC 容器功能與 AOP 功能的實現是其重心所在,在 Spring 中實現了持久層、MVC Web 框架以及各種企業服務的 API 封裝,它們的實現有些依重於 Spring 的 IoC 容器與 AOP 功能,Spring 的這些子框架或封裝的API 功能彼此可以獨立,也可以結合其它的框架方案加以替代,Spring 希望提供 one-stop shop 的框架整合方案。

� 持久層

Spring 提供對持久層的整合,如對 JDBC 的使用加以封裝與簡化,提供編程式交易(Programmatic Transaction)與宣告式交易(Declarative Transaction)管理功能,對於 O/R Mapping 工具(Hibernate、iBATIS)的整合及使用上的簡化,Spring 也提供了對應的解決方案。

� Web 框架

Spring 也提供 Model 2(Web MVC)框架的解決方案,使用 Spring Web框架的好處是可以善用其本身 IoC 與 AOP 的功能,您甚至可以輕鬆的抽換使用不同的 View 層技術,例如使用 JSP、結合 Tiles、使用 PDF 作為展現給使用者的畫面技術。 您也可以將自己所熟悉的 Web 框架與 Spring 整合,像是 Struts、JSF 等,都可以與 Spring 整合,而適用於當前所進行的應用程式。

Page 6: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�6

� 其它企業服務的封裝 對於一些服務,例如 JNDI、Mail、排程(Scheduling)、遠程(Remoting)等,Spring 不直接提供實作,而是採取抽象層方式對這些服務進行封裝,讓這些服務在使用時可以有一致的使用模型,並且在使用上更為簡化。 如果您沒有使用過其它框架的經驗,只是希望藉由 Spring 的學習來吸收前人開發的經驗,面對以上這些術語可能還是不太能了解,在 Spring 中這些概念都有具體的實現,您可以在往後的章節從實作中體會。

Page 7: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�7

1.2 控制反轉(Inversion of Control)

Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」。轉移是相對於過去不良的應用程式設計來說的,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC的一種表現。 一連串抽象的文字轟炸之後,到底 IoC 是什麼意思? IoC 全名Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。初看 IoC,從字面上不容易瞭解其意義,要瞭解 IoC,可先從 Dependency

Inversion 開始瞭解,也就是依賴關係的反轉,看看過去有哪些不良的依賴關係設計,而又要如何將這種不良的依賴關係反轉,進而改進應用程式的可重用性。 簡單的說,在進行模組設計時,高層的抽象模組通常是與商務邏輯(Business logic)相關的模組,它應該具有重用性,而不依賴於低層的實作模組。例如低層模組可能是與硬體相關的軟碟機存取設計,而高層模組是個存檔備份的程式需求,如果高層模組直接呼叫執行低層模組的函式,就對低層模組產生了依賴關係。

Page 8: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�8

圖 1.1 高層模組依賴低層模組實作 在圖 1.1中,應用程式在需要儲存需求時直接執行了 saveToFloppy(),導致高層應用程式依賴於低層模組的 API。假設今天要將應用程式移植至另一個平台上,而該平台使用 USB磁碟作為儲存媒體,則這個應用程式無法直接重用,必須加以修改才行。在這個例子中,由於低層模組的儲存媒體的更動,造成了高層模組也必須跟著更動,這不是一個好的設計方式。在設計上希望模組都依賴於模組的抽象,這樣才可以重複使用高層的應用程式設計。 若以物件導向的方式來設計,依賴反轉(Dependency Inversion)解釋為「程式不應依賴實作,而是依賴於抽象介面」。以 Java程式為例:

public class Business {

private FloppyWriter writer = new FloppyWriter();

....

public void save() {

...

writer.saveToFloppy();

}

}

直接在高層的應用程式中呼叫低層模組的 API,導致應用程式對低層模組產生依賴…

Page 9: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1��

Business 類別的設計中,存檔的需求依賴於實際的 FloppyWriter 物件,如果今天想要將儲存媒介改為 USB 磁碟,則必須修改 Business 的程式,您無法直接重複使用 Business類別。 如果透過介面的宣告,可以改進此一情況,例如可先宣告一個IDeviceWriter介面:

public interface IDeviceWriter {

public void saveToDevice();

} 接著所設計的 Business類別,在遇到存檔需求時,可以設計為依賴於IDeviceWriter介面,而不是依賴於實際的 FloppyWriter,例如:

public class Business {

private IDeviceWriter writer;

public void setDeviceWriter(IDeviceWriter writer) {

this.writer = writer;

}

public void save() {

....

writer.saveToDevice();

}

} 在這樣的設計下,Business 類別就是可以重用的,如果今天有儲存至Floppy 或 USB 磁碟的需求,只要針對這兩種儲存需求分別實作 IDevice-

Writer介面即可,例如針對軟碟機的儲存設計一個 FloppyWriter類別:

public class FloppyWriter implement IDeviceWriter {

public void saveToDevice() {

....

// 實際儲存至實際儲存至實際儲存至實際儲存至 Floppy的程式碼的程式碼的程式碼的程式碼

}

}

Page 10: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�1�

或是針對 USB磁碟的儲存設計一個 UsbDiskWriter類別:

public class UsbDiskWriter implement IDeviceWriter {

public void saveToDevice() {

....

// 實際儲存至實際儲存至實際儲存至實際儲存至 UsbDisk的程式碼的程式碼的程式碼的程式碼

}

} 如果應用程式需要軟碟機儲存的話,可以撰寫一個組態程式如下:

Business business = new Business();

business.setDeviceWriter(new FloppyWriter());

business.save(); 同樣的,如果應用程式需要 USB磁碟儲存的話,可以撰寫一個組態程式如下:

Business business = new Business();

business.setDeviceWriter(new UsbDiskWriter());

business.save(); 可以看到,無論低層的儲存實作如何變動,對於 Business類別來說都無需任何修改。您也可以撰寫一個組態管理程式,藉由一個簡單的 XML或是 .properties檔案來更改組態,如此連以上的組態程式都不必撰寫,就可以簡單的更換 Business 類別所依賴的 IDeviceWriter 實作,例如寫個簡單的 BusinessFactory:

package onlyfun.caterpillar;

import java.io.FileInputStream;

import java.io.IOException;

import java.util.Properties;

public class BusinessFactory {

private static BusinessFactory factory;

private Properties props;

Page 11: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�11

private Business business;

private IDeviceWriter writer;

private BusinessFactory() throws Exception {

props = new Properties();

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

String businessClass = props.getProperty("business.class");

String writerClass = props.getProperty("writer.class");

business = (Business) Class.forName(

businessClass).newInstance();

writer = (IDeviceWriter) Class.forName(

writerClass).newInstance();

business.setDeviceWriter(writer); // 建立依賴關係

}

public static BusinessFactory getInstance() throws Exception {

if (factory == null) {

factory = new BusinessFactory();

}

return factory;

}

public Business getBusiness() {

return business;

}

public IDeviceWriter getWriter() {

return writer;

}

} 這個簡單的 BusinessFactory 將從 config.properties 檔案中讀取business.class與 writer.class設定,並自動完成 Buniness對 IDeviceWriter實作的依賴。假設 config.properties這麼設定:

business.class=onlyfun.caterpillar.Business

writer.class=onlyfun.caterpillar.FloppyWriter

Page 12: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�12

則在程式中,可以如下透過 BusinessFactory 取得 Business 實例,並執行其 save()方法:

business = BusinessFactory.getInstance().getBusiness();

business.save(); 根據 config.properties的設定,Business執行 save()方法時,實際上所使用的將是 FloppyWriter 實例的 saveToDevice()實作,如果 config.

properties 中 writer.class 換成 onlyfun.caterpillar.UsbDiskWriter,則以上的程式片段, Business 執行 save()方法時,實際上所使用的將是UsbDiskWriter實例的 saveToDevice()實作。 透過一個簡單的 BusinessFactory實作,可以將物件的建立、依賴關係的建立隔離至應用程式的主要元件之外,事實上,Spring核心容器就提供這樣的組態管理功能,而且功能更為強大,在第 2章將可以看到如何使用Spring的 IoC容器功能,來設定 Business的依賴關係。

BusinessFactory 完 整 的 程 式 示 範 , 可 以 參 考 光 碟 中 的BusinessFactoryDemo 專案。 從以上的介紹來看,Dependency Inversion 的意思即是「程式不依賴於實作,而是程式與實作都要依賴於抽象」。

IoC的 Control是控制的意思,其實背後的意義也是一種依賴關係的轉移。如果 A依賴於 B,其意義即是 B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,將控制權由實作的一方轉移至抽象的一方,藉由讓抽象方擁有控制權,可以獲得元件的可重用性。在上面的 Business 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的IDeviceWriter介面上,而讓 Business依賴於 IDeviceWriter介面,且 Floppy-

Writer、UsbDiskWriter也依賴於 IDeviceWriter介面。

Page 13: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�13

程式的商務邏輯部份應該要設計為可以重用的,不應受到所使用框架或容器的影響,這樣將來才有可能轉移整個應用程式的商務邏輯至其它的框架或容器,如果商務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:「Don't call me, I'll call you.」,以程式的術語來說的話,就是「不要向容器要求所需要的(物件)資源,容器會自動將這些物件給您!」。

IoC 要求的是容器不應該(或儘量不要)侵入應用程式,也就是不應出現與容器相依的 API。應用程式本身可以依賴於抽象的介面,容器可以透過這些抽象介面將所需的資源注入至應用程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的特定 API,應用程式本身不會意識到正被容器使用,可以隨時從容器系統中脫離,轉移至其它的容器或框架而不用作任何的修改。

Page 14: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�14

1.3 依賴注入(Dependency Injection)

IoC模式基本上是一個高層的模式概念,在 Martin Fowler的 Inversion

of Control Containers and the Dependency Injection pattern(http://www.

martinfowler.com/articles/injection.html)中談到,實現 IoC有兩種方式:Dependency Injection與 Service Locator,Spring所採用的是Dependency

Injection來實現 IoC,中文翻譯為依賴注入。 依賴注入的意義是:「保留抽象介面,讓組件(Component)依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」 依賴注入在 Martin Fowler 的文章中談到了三種基本實現方式:Interface injection、Setter injection與 Constructor injection。並分別稱其為 Type 1 IoC、Type 2 IoC與 Type 3 IoC。

� Type 2 IoC、Type 3 IoC 在前一個小節中所提到的 Business 所實現的是 Type 2 IoC,透過 Setter(也就是 setXXX 方法)注入所依賴的物件,而 Type 3 IoC,則是在建構式(Constructor)上注入依賴關係,例如:

public class BusinessObject {

private IDeviceWriter writer;

public BusinessObject(IDeviceWriter writer) {

this.writer = writer;

}

public void save() {

....

writer.saveToDevice();

}

}

Page 15: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�1�

Spring 鼓勵使用 Setter injection(也就是 Type 2 IoC),但也允許您使用Constructor injection(Type 3 Ioc)。要使用 Setter 或是 Constructor 來完成依賴關係注入,完全視您的需求而定,使用建構方法注入依賴物件的好處之一是,可以在建構物件的同時,一併完成依賴關係的建立,然而如果要建立的物件關係很多,則必須在建構式上宣告一長串的參數,這時使用Setter 來注入依賴關係會是個不錯的選擇,因為 Setter 有明確的方法名稱可用於瞭解注入物件的作用或類型,使用 setXXX() 這樣的名稱,會比必須記憶或查詢建構方法上,某個參數位置所代表的物件要來得好。

� Type 1 IoC

Type 1 IoC 是 Interface injection,使用 Type 1 IoC 時會要求實作介面,物件所在的容器也會使用這個介面,容器知道介面上所規定的方法,所以可呼叫實作介面的物件來完成依賴關係的注入,例如容器的 API 中宣告一個IDependency:

public interface IDependency {

public void createDependency(Map dependObjects);

} 接著,讓 Business 類別實作 IDependency 介面:

public class Business implement IDependency {

private Map dependObjects; // 用以記錄所依賴的物件

public void createDependency(Map dependObjects) {

this.dependObject = dependObjects;

// 在這邊實現與在這邊實現與在這邊實現與在這邊實現與 Business的依賴關係的依賴關係的依賴關係的依賴關係

......

}

public void save() {

....

writer.saveToDevice();

}

}

Page 16: Spring 2.0 技術手冊第一章 - 認識 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

1�16

如果要完成依賴關係注入的物件,必須實現 IDependency 介面,並讓容器來管理物件,容器會執行被管理物件的 createDependency() 方法來完成依賴關係的建立。 在上面的例子中,由於 Type 1 IoC要求 Business實現容器所規定的介面,這就使得 Business依賴於容器的 API,如果日後 Business打算脫離目前這個容器至其它的容器或框架之中,就必須修改程式(也就是修改實作IDependency 介面的程式部份)。想想這樣的實作方式,在更複雜的依賴關係中將產生更多複雜的介面,組件與容器(框架)的依賴會更加複雜,最後使得組件無法從容器中脫離。 所以 Type 1 IoC對應用程式或組件來說具有較強的侵入性,使用它來實現依賴注入,會使得應用程式或組件相依於容器(框架),因而降低組件的重用性。

Spring 的核心是個 IoC 容器,可以管理物件的生命週期,並可以用Setter 或建構式的方式,透過撰寫組態檔案(一個 XML 檔案或是一個 .properties檔案),讓 Spring在執行時期根據組態檔的設定,為您建立物件之間的依賴關係,您不必特地撰寫一些程式來自行建立這些物件之間的依賴關係(如之前的 BusinessFactory),這不僅減少了大量的程式撰寫、降低了物件之間的耦合程度,透過 Spring這類成熟的框架,更可以大幅提升應用程式的可靠性。

Page 17: Spring 2.0 技術手冊第一章 - 認識 Spring

Chapter 1 認識 Spring

1�17

1.4 接下來的主題 關於入門 Spring所需認識的術語及概念就先談到這邊,從下一個章節開始,將直接從 Spring的設定與使用中,體會各個在這個章節介紹到的觀念,首先將介紹如何在 Eclipse中撰寫第一個 Spring程式,並了解如何在Eclipse中使用 Spring IDE。

圖 1.2 不要呼叫容器,容器會給您所想要的物件