78
IoC 容器 Spring Container IoC BeanFactory ApplicationContext Spring Spring Bean Application framework ApplicationContext BeanFactory 3

Spring 2.0 技術手冊第三章 - IoC 容器

Embed Size (px)

Citation preview

Page 1: Spring 2.0 技術手冊第三章 - IoC 容器

IoC容器

Spring 的核心是一個容器(Container),實作了 IoC 的概念,可以協助管理各個物件的生命週期,以及物件之間的依賴關係。在核心容器的使用上,熟悉 BeanFactory 與 ApplicationContext 的運用是了解 Spring 的重點所在,在這一個章節中,將可以了解到如何使用Spring 容器進行各種 Bean 的組態與管理。 在另一方面,作為一個應用程式框架(Application framework),ApplicationContext 除了具備如 BeanFactory 基本的容器管理功能之外,並支援更多應用程式框架的特性,像是資源的取得、文字訊息解析、事件的處理與傳播等功能。

3

Page 2: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�2

3.1 Bean基本管理 在第 2章中已經實際完成第一個 Spring程式,並大致了解何謂「依賴注入」(Dependency Injection)、如何使用 Spring的容器功能來管理 Bean,在這一個小節當中,將會學習到更多有關於 Bean在 Spring中的設定方式,以及生命週期。

3.1.1 BeanFactory、ApplicationContext

BeanFactory負責讀取 Bean定義檔,管理物件的載入、生成,維護 Bean物件與 Bean物件之間的依賴關係,負責 Bean的生命週期,對於簡單的應用程式來說,使用 BeanFactory就已經足夠來管理 Bean,在物件的管理上就可以獲得許多的方便性,BeanFactory介面包括了六個方法可以呼叫:

� boolean containsBean(String) 測試 BeanFactory 中是否包括所指定名稱的 Bean。

� Object getBean(String) 指定 Bean 定義檔中設定的名稱,即可取得相對應的 Bean 實例。

� Object getBean(String, Class) 指定 Bean 定義檔中設定的名稱,取得相對應的 Bean 實例,並轉換(Cast)至指定的類別。

� Class getType(String name) 指定 Bean 定義檔中設定的名稱,取得相對應的 Bean 之 Class 實例。

� boolean isSingleton(String) 指定 Bean 定義檔中設定的名稱,測試指定的 Bean 之 scope 是否為Singleton(之後在 Bean 的 scope 中會說明)。

� String[] getAliases(String)

Page 3: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�3

指定 Bean 定義檔中設定的名稱,取得該 Bean 所有的別名(之後在 Bean的識別名稱與別名中會說明)。 不過作為一個應用程式框架,只提供 Bean容器管理的功能是不夠的,若要利用 Spring 所提供的一些特色以及進階的容器功能,則可以使用org.springframework.context.ApplicationContext。 ApplicationContext基於 BeanFactory而建立,也具有負責讀取 Bean定義檔,維護 Bean之間的依賴關係等功能,除此 ApplicationContext 還提供一個應用程式所需的更完整的框架功能,例如:

� 提供取得資源檔案(Resource file)更方便的方法。

� 提供文字訊息解析的方法。

� 支援國際化(Internationalization, I18N)訊息。

� ApplicationContext 可以發佈事件,對事件感興趣的 Bean 可以接收到這些事件。

Spring 的創始者 Rod Johnson 建議使用 ApplicationContext 來取代BeanFactory,在實作 ApplicationContext 的類別中,最常使用的大概是以下三個:

� org.springframework.context.support.FileSystemXmlApplicationContext 可指定 XML 定義檔的相對路徑或絕對路徑來讀取定義檔。

� org.springframework.context.support.ClassPathXmlApplicationContext 從 Classpath 設定路徑中來讀取 XML 定義檔。

� org.springframework.web.context.support.XmlWebApplicationContext 在 Web 應用程式中的檔案架構中,指定相對位置來讀取定義檔。 舉個簡單的例子來說,可以將第 2章中所完成的第一個 Spring程式中的 SpringDemo類別修改為以下的內容:

Page 4: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�4

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

HelloBean hello =

(HelloBean) context.getBean("helloBean");

System.out.println(hello.getHelloWord());

}

}

ApplicationContext 可以讀取多個 Bean 定義檔,可以在實例化ApplicationContext的實作類別時,以陣列指定 Bean定義檔的位置,例如:

ApplicationContext context = new ClassPathXmlApplicationContext(

new String[] {"beans-config.xml", "beans-config2.xml"}); 您可以使用 file:/、 classpath:甚至 http://等 URL 前置,甚至classpath*:,表示所有的 Classpath前置路徑都匹配:

ApplicationContext context =

new ClassPathXmlApplicationContext("classpath*:beans-config.xml"); 也可以指定*字元,例如以下的例子可讀取 Classpath 下所有"beans"開頭的 XML 設定檔案,但要注意的是只在實際的檔案系統中有用,如果是在.jar檔案中的話,則以下的指定就無效:

ApplicationContext context =

new ClassPathXmlApplicationContext("beans*.xml");

Page 5: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3��

如果您先前只將 spring-beans.jar 與 spring-core.jar 加入至 Classpath中,則為了能編譯與執行以上的程式,還必須將 spring-context.jar 加入至 Classpath的設定中。 當需要多個 Bean 定義檔時, Spring 開發團隊建議以上使用ApplicationContext的方式來讀取,好處是 Bean定義檔之間各自獨立,不用意識到彼此的存在。另一個替代的方式是使用<import>標籤,例如:

<beans ...>

<import resource="dao-config.xml"/>

<import resource="resources/messageSource.xml"/>

<bean id="bean1" class="..."/>

<bean id="bean2" class="..."/>

</beans>

<import>標籤必須放置在<bean>標籤之前,定義檔必須放置在同一個目錄或是 Classpath之中,以相對路徑指定 Bean定義檔位置,而每個定義檔的內容都必須包括<beans>根標籤。 在這邊先介紹在撰寫程式時,如何使用 ApplicationContext 替換BeanFactory,關於 ApplicationContext於文字訊息的管理、事件的發佈等功能,也將在往後相關的主題中一一介紹。

3.1.2 Bean的識別名稱與別名 在定義檔中使用<bean>標籤時可以使用"id"指定 Bean的識別名稱,當您需要多個 Bean定義檔時,最好規範"id"名稱的命名方式,以免名稱上的衝突。在設定"id"屬性之後,您還可以為 Bean設定別名,例如考慮在 A定義檔中要參考一個"device:dataSource"的 Bean實例,而在 B定義檔中要參考一個"user:dataSource"的 Bean 實例,但實際上 DataSource 的實例在應

Page 6: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�6

用程式中只存在一個,則就可以使用別名的方式為該實例指定"device:dataSource"與"user:dataSource"的名稱,例如:

<beans ...>

<bean id="app:dataSource" class="..."/>

<alias name="app:dataSource" alias="device:dataSource"/>

<alias name="app:dataSource" alias="user:dataSource"/>

...

</beans> 則在 A定義檔中,可以直接參考至別名,例如:

<beans ...>

<bean id="device:someBean" class="...">

<property>

<ref bean="device:dataSource">

</property>

</bean>

...

</beans> 在 B定義檔中,也可以參考至另一個別名,例如:

<beans ...>

<bean id="user:otherBean" class="...">

<property>

<ref bean="user:dataSource">

</property>

</bean>

...

</beans> 兩個定義檔可以擁有自己的名稱設定規範,而實際上參考的是同一個Bean 實例,除了使用<alias>標籤設定別名之外,還可以直接使用<bean>標籤的"name"屬性來設定別名,而多個別名之間以逗號區隔,例如:

<beans ...>

<bean id="app:dataSource"

name="device:dataSource,user:dataSoource" class="..."/>

...

</beans>

Page 7: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�7

3.1.3 Bean的實例化 在 Spring中要實例化一個 Bean可以有幾種方式,到目前為止所看到的是最基本的方式:

<bean id="writer" class="onlyfun.caterpillar.FloppyWriter"/> 依這樣的設定方式,Spring 將會使用預設建構式,也就是沒有參數的建構式來建立 Bean實例(關於有參數的建構式的設定,下一節將會說明)。 在設計上的一種方式是透過靜態工廠方法(static factory method)來取得某個物件,好處是呼叫靜態工廠方法的物件不用了解物件建立的細節,例如可以設計一個 IMusicBox介面:

StaticFactoryMethodDemo IMusicBox.java

package onlyfun.caterpillar;

public interface IMusicBox {

public void play();

} 假設您設計了一個 MusicBoxFactory,取得 IBox 實例的細節由它的createMusicBox()靜態方法負責:

StaticFactoryMethodDemo MusicBoxFactory.java

package onlyfun.caterpillar;

public class MusicBoxFactory {

public static IMusicBox createMusicBox() {

return new IMusicBox() {

public void play() {

System.out.println("撥放鋼琴音樂…");

}

};

}

}

Page 8: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�8

在 Spring中也可以設定使用靜態工廠方法來取得 Bean實例,例如若要透過 MusicBoxFactory的 createMusicBox()方法來取得 IMusicBox的實例,則可以設定<bean>的"factory-method"屬性,例如:

StaticFactoryMethodDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="musicBox"

class="onlyfun.caterpillar.MusicBoxFactory"

factory-method="createMusicBox"/>

</beans> 接下來可以撰寫一個簡單的測試程式,看看以上的設定是否可正確取得 IMusicBox的實例:

StaticFactoryMethodDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans-config.xml");

IMusicBox musicBox = (IMusicBox) context.getBean("musicBox");

musicBox.play();

}

}

Page 9: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3��

執行的結果如下圖所示:

圖 3.1 StaticFactoryMethodDemo 專案執行結果 與這個範例類似的,如果您想要使用某個工廠 Bean 實例的工廠方法(factory method)來取得 Bean實例,則可以使用<bean>的"factory-bean"屬性指定工廠 Bean,並使用"factory-metod"屬性指定工廠方法來取得Bean實例,例如一個設定的範例片段如下:

<!—工廠 Bean,其上有 createInstance()方法 -->

<bean id="factoryBean" class="onlyfun.caterpillar.SomeFactory">

...

</bean>

<bean id="some"

factory-bean="factoryBean"

factory-method="createInstance"/> 在以上的設定中,Spring將會實例化"factoryBean",而要取得"some"的實例時,將會使用"factoryBean"的 createInstance()方法來取得。

3.1.4 Bean的 scope 在 Spring中,從 BeanFactory或 ApplicationContext取得的實例預設為 Singleton,也就是預設為每一個 Bean名稱只維持一個實例,例如在第2章 SpringDemo專案中,每一次透過 factory.getBean ("helloBean") 取得的物件,實際上都是同一個物件,而不是每一次都產生一個新的物件。 使用 Singleton模式產生單一實例,對單執行緒的程式來說並不會有什麼問題,但對於多執行緒的程式,您必須注意到執行緒安全(Thread-safe)

Page 10: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�1�

的議題,防止多個執行緒同時存取共用資源所引發的資料不同步問題,通常 Singleton的 Bean都是無狀態的(Stateless)。 然而在 Spring中可以設定,每次從 BeanFactory或 ApplicationContext指定別名並取得 Bean時都產生一個新的實例,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

scope="prototype">

... 在 Spring 中,"scope" 屬性預設是 "singleton",藉由將其設定為

"prototype",則每次指定名稱來取得 Bean 時,都會產生一個新的實例。您也可以藉由設定<bean>的"singleton"屬性為"true"或"false",來設定產生實例是否為 Singleton的方式,設定"singleton"屬性的方式,主要是為了相容於先前版本的 Spring設定方式而保留。 要注意到,Spring 的 Singleton 主要是針對「一個 IoC 容器維持一個Bean實例」而言,跟設計模式(Design Pattern)上常談到的 Singleton不同,設計模式上談到的 Singleton,所說的是對每個 ClassLoader所載入的類別產生一個實例。 在 Spring 2.0中,"scope"屬性除了可以設定"singleton"與"prototype"之外,針對 Web 應用程式環境,還可以設定 "request"、 "session"與"globalSession",分別表示請求階段、會話階段與基於 Portlet的 Web應用程式會話階段。

Page 11: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�11

3.1.5 Bean的生命周期 一個 Bean 從建立到銷毀,會歷經幾個執行階段,如果是使用BeanFactory來生成、管理 Bean的話,會儘量支援以下的生命週期:

� Bean 的建立 由 BeanFactory 讀取 Bean 定義檔,並生成各個 Bean 實例。

� 屬性注入 執行相關的 Bean 屬性依賴注入。

� BeanNameAware 的 setBeanName() 如果 Bean 類別有實作 org.springframework.beans.factory.BeanNameAware介面,則執行它的 setBeanName() 方法。

� BeanFactoryAware 的 setBeanFactory() 如 果 Bean 類 別 有 實 作 org.springframework.beans.factory.

BeanFactoryAware 介面,則執行它的 setBeanFactory() 方法。

� BeanPostProcessors 的 processBeforeInitialization() 如 果 有 任 何 的 org.springframework.beans.factory.config.BeanPost-

Processors 實例與 Bean 實例關聯,則執行 BeanPostProcessors 實例的processBeforeInitialization() 方法。

� InitializingBean 的 afterPropertiesSet() 如果 Bean 類別有實作 org.springframework.beans.factory.InitializingBean,則執行它的 afterPropertiesSet() 方法。

� Bean 定義檔中定義 init-method 可以在 Bean 定義檔使用 "init-method" 屬性設定方法名稱,例如:

...

<bean id="helloBean"

Page 12: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�12

class="onlyfun.caterpillar.HelloBean"

init-method="initBean">

... 如果有以上設定的話,則進行至這個階段時,就會執行 initBean() 方法。

� BeanPostProcessors 的 processaAfterInitialization() 如果有任何的 BeanPostProcessors 實例與 Bean 實例關聯,則執行 Bean-

PostProcessors 實例的 processaAfterInitialization() 方法。

� DisposableBean 的 destroy() 在容器關閉時,如果 Bean類別有實作 org.springframework.beans.factory.

DisposableBean 介面,則執行它的 destroy() 方法。

� Bean 定義檔中定義 destroy-method 在容器關閉時,可以在 Bean 定義檔使用 "destroy-method" 屬性設定方法名稱,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

destroy-method="destroyBean">

... 如果有以上設定的話,則進行至這個階段時,就會執行 destroyBean() 方法。 定義 <bean>的 "init-method"屬性,其實與實作 InitializingBean 的afterPropertiesSet()意義是相同的,採用前者,可以定義 Bean的初始方法,而又不用耦合至 Spring 的 API。定義<bean>的"destroy-method"屬性,與實作 DisposableBean 介面的 destroy() 方法意義也是相同,採用前者,可以讓 Bean不用耦合至 Spring的 API。 如果所有的 Bean都有固定的初始名稱或銷毀名稱,例如都命名為 init()或 destroy(),則不必在<bean>上分別定義"init-method"與"destroy-method"

Page 13: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�13

屬性,而可以在<beans>上定義"default-init-method"與"default-destroy-

method"屬性,例如:

<beans default-init-method="init"

default-destroy-method="destroy">

....

</beans> 如以上的設定之後,Spring會自動執行每個 Bean上所定義的 init()與destroy()方法,可以省去不少撰寫設定檔的功夫。 如果是使用 ApplicationContext來生成並管理 Bean的話則稍有不同,使用 ApplicationContext 來生成及管理 Bean 實例的話,在執行BeanFactoryAware的 setBeanFactory() 階段之後,若 Bean類別上有實作org.springframework.context.ApplicationContextAware 介面,則執行其setApplicationContext() 方法,接著才繼續進行 BeanPostProcessors 的processBeforeInitialization() 及之後的流程。 在非 Web應用程式中,若想在關閉容器之前並呼叫 Bean定義檔中,Singleton 的 Bean 所 設 定 的 destroy 方 法 , 則 可 以 執 行AbstractApplicationContext 的 registerShutdownHook()方法,向 JVM註冊相關方法,例如:

AbstractApplicationContext context

= new ClassPathXmlApplicationContext("beans-config.xml");

...

context.registerShutdownHook();

// 執行應用程式… 則在應用程式結束之前,Bean定義檔上所設定的 destroy方法將會被呼叫執行。 如果使用 BeanFactory,只有在使用 getBean()方法真正取得 Bean時,才會作實例化的動作。如果使用 ApplicationContext,則會預先針對 Bean定義檔的內容,將所有的 Bean 實例化,如果這不是您所想要的,則可以

Page 14: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�14

在<bean>上設定"lazy-init"屬性為"true",則 ApplicationContext就不會在啟動時,針對該 Bean作實例化的動作,例如:

<bean id="some" class="onlyfun.caterpillar.Some" lazy-init="true">

...

</bean>

3.1.6 Bean定義的繼承 如果 Bean定義檔的內容不斷的增長,而您發現到,有些 Bean的定義其實有所重複,例如也許好幾個 Bean定義都有"name"與"age"等屬性,且大部份都是設定相同的值,只有幾個 Bean 會有不同的設定,則可以考慮繼承某個 Bean定義,這樣可以省去許多設定的功夫。 舉個實際的例子來示範 Bean定義的繼承,假設您撰寫了一個 SomeBean類別如下:

BeanInheritanceDemo SomeBean.java

package onlyfun.caterpillar;

public class SomeBean {

private String name;

private int age;

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

Page 15: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�1�

} 您也許會產生許多 SomeBean的實例,其中大部份的 SomeBean其"name"與"age"屬性都是"guest"與 18,只有幾個不同,則可以如下定義:

BeanInheritanceDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="inheritedSomeBean" abstract="true">

<property name="name">

<value>guest</value>

</property>

<property name="age">

<value>18</value>

</property>

</bean>

<bean id="some"

class="onlyfun.caterpillar.SomeBean"

parent="inheritedSomeBean">

<property name="name">

<value>Justin</value>

</property>

</bean>

</beans> 在"inheritedSomeBean"中設定了"abstract"屬性為"true",表示這是個抽象的 Bean定義,Spring不會嘗試去實例化,而在"some"中設定了"parent"屬性,表示它將繼承"inheritedSomeBean"的設定,只有"name"屬性重新定義為"Justin"。可以寫個簡單的程式來看看"some"這個 Bean最後的屬性設定為何:

Page 16: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�16

BeanInheritanceDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans-config.xml");

SomeBean some = (SomeBean) context.getBean("some");

System.out.println("name:" + some.getName());

System.out.println("age:" + some.getAge());

}

} 可以在執行結果中看到,"name"被重新定義為"Justin",而"age"則繼承了"inheritedSomeBean",也就是 18:

圖 3.2 BeanInheritanceDemo 專案執行結果 除了從一個完全抽象的 Bean 定義繼承相關設定之外,也可以從一個Bean實例之定義來繼承,例如:

...

<bean id="inheritedSomeBean" class="onlyfun.caterpillar.SomeBean">

<property name="name">

<value>guest</value>

</property>

<property name="age">

<value>18</value>

Page 17: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�17

</property>

</bean>

<bean id="some"

class="onlyfun.caterpillar.SomeBean"

parent="inheritedSomeBean">

<property name="name">

<value>Justin</value>

</property>

</bean> 在這個設定中,"inheritedSomeBean"也可以被 Spring實例化,若有必要,也可以被其它 Bean繼承其定義。

Page 18: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�18

3.2 Bean的依賴設定 在 Spring 中兩種基本的依賴注入方式是 Setter Injection 及Constructor Injection,另外針對非 Singleton 的依賴注入,還提供了Method Injection,在這個小節中,將針對這些依賴注入,以及一些依賴關係設定的方式加以說明。

3.2.1 Type 2 IoC、Type 3 IoC 在第 2章中所完成的第一個 Spring程式中,利用 Bean的 Setter方法完成依賴注入, Spring 鼓勵的是 Setter Injection,也就是 Type 2

Dependency Injection,但也允許使用 Type 3 Dependency Injection 的Constructor injection,要使用 Setter或 Constructor來注入依賴關係是視需求而定,這在稍後再加以討論,這邊先來看看如何在 Spring 中使用Constructor injection,首先看看 HelloBean類別如何撰寫:

Type3Demo HelloBean.java

package onlyfun.caterpillar;

public class HelloBean {

private String name;

private String helloWord;

// 建議要有無參數建構方法

public HelloBean() {

}

public HelloBean(String name, String helloWord) {

this.name = name;

this.helloWord = helloWord;

}

public void setName(String name) {

this.name = name;

}

Page 19: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�1�

public String getName() {

return name;

}

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

return helloWord;

}

} 在定義 Bean類別時,為了要能讓 Spring可以有使用無參數建構方法來生成物件的彈性,建議可以定義一個無參數的建構方法,即使目前沒有撰寫任何的實作內容。 在 HelloBean 類別定義中要注意的是,第二個有參數的建構方法上的兩個參數之順序,Bean定義檔中使用 Constructor Injection時,在設定上可以依建構方法上參數的順序來指定,如下所示:

Type3Demo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg>

<value>Justin</value>

</constructor-arg>

<constructor-arg>

<value>Hello</value>

</constructor-arg>

</bean>

</beans>

Page 20: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�2�

在定義檔案中,使用 <constructor-arg> 標籤來表示將使用 Constructor

Injection,由於使用 Constructor Injection 並不如 Setter Injection 時擁有setXXX() 這樣易懂的名稱,所以必須指定時必須依參數的順序。另一個方式是指定索引位置,"index"屬性就是用於指定物件將注入至建構方法中的哪一個位置的參數,參數的順序指定中,第一個參數的索引值是 0,第二個是 1,依此類推,例如 beans-config.xml中也可以這麼撰寫:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg index="0">

<value>Justin</value>

</constructor-arg>

<constructor-arg index="1">

<value>Hello</value>

</constructor-arg>

</bean>

... 接著是撰寫主程式,使用 ApplicationContext 讀取定義檔案內容、生成 Bean實例、完成依賴關係注入,程式的撰寫如下所示:

Type3Demo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

HelloBean hello =

(HelloBean) context.getBean("helloBean");

Page 21: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�21

System.out.print("Name: ");

System.out.println(hello.getName());

System.out.print("Word: ");

System.out.println(hello.getHelloWord());

}

} 執行結果如下所示:

圖 3.3 Type3Demo 專案執行結果 當建構式上的參數個數相同時,Spring 會自動解析建構式上的參數型態及所設定的依賴注入,以決定要使用哪個建構式,例如:

...

public class HelloBean {

...

public HelloBean(String name) {

...

}

public HelloBean(Date date) {

...

}

...

}

HelloBean在建構時可以指定 String,也可以指定 java.util.Date物件,如果在設定檔中是這麼設定:

Page 22: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�22

...

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg>

<ref bean="date"/>

</constructor-arg>

</bean>

... 由於設定檔中是注入 Date 型態的物件,則 Spring 會自動解析並使用有 Date型態參數的建構式,來建構物件並注入所依賴的 Date物件,您也可以使用"type"屬性來明確指定要使用哪個型態,例如:

...

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg type="java.util.Date">

<ref bean="date"/>

</constructor-arg>

</bean>

... 至於要使用 Constructor或 Setter方法來完成依賴注入這個問題,其實就等於在討論一個古老的問題:要在物件建立時就準備好所有的資源,或是在物件建立好後,再使用 Setter方法來進行設定。 使用 Constructor的好處之一是,可以在建構物件的同時一併完成依賴關係的建立,物件一建立後,它與其它物件的依賴關係也就準備好了,但如果要建立的物件關係很多,使用 Constructor injection會在建構方法上留下一長串的參數,且不易記憶,這時使用 Setter方法會是個不錯的選擇。 另一方面,Setter 方法具有明確的方法名稱可以瞭解注入的物件會是什麼,像是 setXXX() 這樣的名稱,會比記憶 Constructor上某個參數位置

Page 23: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�23

的索引代表某個物件來得好,當結合 IDE的方法提示功能使用時,撰寫程式會更方便且有效率。 然而使用 Setter 方法時,由於提供有 setXXX() 方法,所以不能保證相關的資料成員或資源在執行時期不會被更改設定,因為程式開發人員可能直接執行 Setter方法來設定相關屬性,所以如果想要讓一些資料成員或資源變為唯讀或是私有,使用 Constructor injection會是個簡單的選擇。

3.2.2 依賴的值設定與參考 之前的例子在定義 Bean時,可以在<property>或<constructor>中直接使用<value>標籤,指定一個字串或基本型態(primitive)給屬性或建構式,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg>

<value>Justin</value>

</constructor-arg>

<property name="age">

<value>18</value>

</property>

</bean>

... 您也可以直接使用"value"屬性來指定字串或基本型態值,這是一種比較簡潔的設定方式,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg value="Justin"/>

<property name="age" value="18"/>

</bean>

...

Page 24: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�24

如果想要設定某個屬性為 null值,可以使用<null/>標籤,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="name"><null/></property>

</bean>

... 注意以下的設定方式,是將字串屬性設定為空字串"",而不是設定為null:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="name"><value></value></property>

</bean>

... 如果在 Bean定義檔中已經有一個定義的 Bean實例,則可以直接讓某個屬性參考至這個實例,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg>

<ref bean="date"/>

</constructor-arg>

<property name="other">

<ref bean="otherBean"/>

</property>

</bean>

... 另一個比較簡潔的寫法,是使用"ref"屬性來指定,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<constructor-arg ref="date"/>

<property name="other" ref="otherBean"/>

</bean>

...

Page 25: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�2�

如果希望使用<ref>參考其它 Bean實例時,所定義的 Bean必須是在同一個設定檔案之中,則可以指定"local"屬性,例如:

...

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="other">

<ref local="otherBean"/>

</property>

</bean>

... 如果某個 Bean 的實例只被某個屬性參考一次,之後在定義檔中不再被其它 Bean的屬性所參考,那麼也可以直接在屬性定義時使用 <bean> 標籤,並僅需指定其 "class" 屬性即可,例如:

...

<bean id="helloBean" class="onlyfun.caterpillar.HelloBean">

<property name="helloWord">

<value>Hello!</value>

</property>

<property name="date">

<bean class="java.util.Date"/>

</property>

</bean>

...

Spring的 IoC容器會自動生成 Date實例,並透過 setDate() 方法將 Date實例設定給"helloBean"。 在取得某個 Bean之前,如果它依賴於另一個 Bean,Spring就會先去實例化被依賴的 Bean並進行依賴注入。如果某個 Bean在生成之前,要求另一個 Bean必須先實例化,則可以指定"depends-on"屬性來指定,例如:

Page 26: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�26

<bean id="beanOne"

class="onlyfun.caterpillar.BeanOne" depends-on="beanTwo"/>

<bean id="beanTwo" class=onlyfun.caterpillar.BeanTwo" /> 在上例中,雖然"beanOne"並沒有明確要求"beanTwo"注入,但由於設定了"depends-on"屬性為"beanTwo",則在取得"beanOne"之前,Spring會先將"beanTwo"實例化,如果有兩個以上的 Bean 要設定在"depends-on"中,則以逗號區隔。

3.2.3 自動綁定 除了在 Bean定義檔中使用<value>指定字串、基本型態值、使用 <ref> 直接指定參考至其它 Bean實例,或是使用 <bean> 標籤並指定 "class" 屬性來指定相依物件,Spring 也支援隱式的自動綁定。您可以透過類型(byType)或名稱(byName),將某個 Bean實例綁定至其它 Bean對應的屬性,這邊直接以 AutoWireDemo專案中的程式為例,示範各種綁定的方式,假設 HelloBean類別如下定義:

AutoWireDemo HelloBean.java

package onlyfun.caterpillar;

import java.util.Date;

public class HelloBean {

private String helloWord;

private Date date;

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

return helloWord;

Page 27: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�27

}

public void setDate(Date date) {

this.date = date;

}

public Date getDate() {

return date;

}

} 其中HelloBean的 setDate() 方法接受一個 Date的實例,在以下的 Bean定義檔中,將"autowire"屬性設定為"byType",讓 Spring自動依型態作依賴綁定:

AutoWireDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="dateBean" class="java.util.Date"/>

<bean id="helloBean" class="onlyfun.caterpillar.HelloBean"

autowire="byType">

<property name="helloWord" value="Hello!"/>

</bean>

</beans> 在定義檔中,並沒有指定"helloBean"的 Date 屬性,而是透過自動綁定,在 "autowire" 屬性上指定了 "byType",所以會根據"helloBean"的setDate() 方法所接受的型態,來判斷在 Bean 定義檔中是否定義有類似的型態物件,並將之設定給"helloBean"的 setDate(),使用自動綁定時,如果

"byType" 無法完成綁定,則丟出 org.springrframework.beans.factory.

Page 28: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�28

UnsatisfiedDependencyExcpetion 例外。可以撰寫一個簡單的測試程式,看看依賴關係是否完成:

AutoWireDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans-config.xml");

HelloBean hello =

(HelloBean) context.getBean("helloBean");

System.out.print("Word: ");

System.out.println(hello.getHelloWord());

System.out.print("Date: ");

System.out.println(hello.getDate());

}

} 執行結果參考畫面如下所示:

圖 3.4 AutoWireDemo 專案執行結果

Page 29: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�2�

您也可以指定 "byName" 來綁定,則 Spring會根據 Bean定義時的 "id" 屬性上指定的別名與 Setter 名稱是否一致來進行自動綁定,舉個例子來說,如果是 "byName" 而要透過 setDate() 方法來完成依賴注入的話,則必須修改一下第一個 Bean 的 "id"值為 "date" 名稱,例如修改 beans-

config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

autowire="byName">

<property name="helloWord" value="Hello!"/>

</bean>

</beans> 如果使用 "byName" 無法完成自動綁定,則對應的 Setter僅維持未綁定狀態。 也可以在使用 Type 3 Dependency Injection時套用自動綁定,也就是在建構方法上也可以嘗試進行自動綁定,例如若修改 AutoWireDemo專案中的 HelloBean類別如下:

package onlyfun.caterpillar;

import java.util.Date;

public class HelloBean {

private String helloWord;

private Date date;

Page 30: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�3�

// 建議保留一個無參數的建構方法

public HelloBean() {

}

public HelloBean(Date date) {

this.date = date;

}

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

return helloWord;

}

public void setDate(Date date) {

this.date = date;

}

public Date getDate() {

return date;

}

} 然後重新定義 beans-config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

autowire="constructor">

<property name="helloWord" value="Hello!"/>

</bean>

</beans>

Page 31: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�31

由於 "autowire" 設定為 "constructor",在建立依賴關係時,Spring容器會試圖比對容器中的 Bean 實例型態,及相關的建構方法上之參數型態,看看在型態上是否符合,如果有的話,則選用該建構方法來建立 Bean實例。在以上例子中,"helloBean"的帶參數建構方法與 date這個 Bean的型態相符,於是選用該建構方法來建構實例,並將定義檔中的 date實例注入給它,如果無法完成綁定,則丟出 org.springframework.beans.factory.

UnsatisfiedDependencyException例外。 可以看到,運用一些自動綁定的方式,可以縮短定義檔的撰寫內容,如果還想再偷懶的話,還可以設定為 "autodetect",一切交給 Spring來判斷,例如修改一下 beans-config.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

autowire="autodetect">

<property name="helloWord" value="Hello!"/>

</bean>

</beans> 當 "autowire" 被設定為 "autodetect" 時, Spring 會嘗試使用如

"autowire" 被設定為 "constructor" 來處理依賴關係的建立,如果沒有完成依賴關係建立,則再嘗試使用如 "autowire" 被設定為 "byType" 的方式來建立依賴關係。

Page 32: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�32

在以上介紹的隱式自動綁定中,由於沒辦法從定義檔中,清楚的看到是否每個屬性都完成設定,為了確定某些依賴關係確實建立,您可以加入相依檢查,在 <bean> 標籤使用時設定 "dependency-check",可以有四種相依檢查方式:"simple"、"objects"、"all"、"none"。

"simple" 只檢查簡單的屬性是否完成依賴關係,像是原生(primitive)資料型態或字串物件;"objects" 設定則檢查物件型態的屬性是否完成依賴關係;"all" 則檢查全部的屬性是否完成依賴關係;"none" 設定是預設值,表示不檢查相依性。 一個設定的例子如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="date" class="java.util.Date"/>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean"

autowire="autodetect"

dependency-check="all">

<property name="helloWord" value="Hello!"/>

</bean>

</beans> 一旦使用自動綁定時加入了相依檢查設定,如果進行相依檢查時發現有未完成的依賴關係,則執行程式時會丟出 org.springframework.beans.

factory.UnsatisfiedDependencyException例外。 由於使用自動綁定,在定義檔上不容易查看出物件之間的依賴關係,因此建議只有在應用程式需要快速開發原型,或需求未確定時,才使用自

Page 33: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�33

動綁定的功能,以簡化初期開發時瑣碎的組態設定工作,之後應用程式功能確定之後,就清楚的設定物件與物件間的依賴關係。

3.2.4 集合物件 對於陣列、java.util.List、java.util.Set、java.util.Map等集合物件,在注入前若必須填充入一些物件至集合中,然後再將集合物件注入至所需的Bean 時,也可以交由 Spring 的 IoC 容器來自動維護或生成集合物件,並完成依賴注入。 這邊直接舉個完整的應用程式作示範,例如若有個 SomeBean類別定義如下:

CollectionDemo SomeBean.java

package onlyfun.caterpillar;

import java.util.List;

import java.util.Map;

public class SomeBean {

private String[] someStrArray;

private Some[] someObjArray;

private List someList;

private Map someMap;

public String[] getSomeStrArray() {

return someStrArray;

}

public void setSomeStrArray(String[] someStrArray) {

this.someStrArray = someStrArray;

}

public Some[] getSomeObjArray() {

return someObjArray;

}

Page 34: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�34

public void setSomeObjArray(Some[] someObjArray) {

this.someObjArray = someObjArray;

}

public List getSomeList() {

return someList;

}

public void setSomeList(List someList) {

this.someList = someList;

}

public Map getSomeMap() {

return someMap;

}

public void setSomeMap(Map someMap) {

this.someMap = someMap;

}

} 在 SomeBean類別中只是簡單的定義一些陣列、List與 Map屬性,稍後這些屬性所需的相依物件將由 Spring來生成注入,在 SomeBean中還使用到 Some類別,其內容如下:

CollectionDemo Some.java

package onlyfun.caterpillar;

public class Some {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

Page 35: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�3�

public String toString() {

return name;

}

} 對於陣列或 List型態的依賴關係注入,在撰寫定義檔時是使用 <list> 標籤,並使用 <value> 標籤指定字串,或是使用 <ref> 來參考至其它的Bean 實例;對於 Map 型態的依賴關係注入則是使用 <map> 標籤,Map必須指定 key-value,所以您要用 <entry> 標籤指定 key,然後使用 <value> 標籤指定字串,或是使用 <ref> 來參考至其它的 Bean實例。

CollectionDemo專案的 beans-config.xml定義撰寫如下:

CollectionDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="some1" class="onlyfun.caterpillar.Some">

<property name="name" value="Justin"/>

</bean>

<bean id="some2" class="onlyfun.caterpillar.Some">

<property name="name" value="momor"/>

</bean>

<bean id="someBean" class="onlyfun.caterpillar.SomeBean">

<property name="someStrArray">

<list>

<value>Hello</value>

<value>Welcome</value>

</list>

</property>

Page 36: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�36

<property name="someObjArray">

<list>

<ref bean="some1"/>

<ref bean="some2"/>

</list>

</property>

<property name="someList">

<list>

<value>ListTest</value>

<ref bean="some1"/>

<ref bean="some2"/>

</list>

</property>

<property name="someMap">

<map>

<entry key="MapTest">

<value>Hello!Justin!</value>

</entry>

<entry key="someKey1">

<ref bean="some1"/>

</entry>

</map>

</property>

</bean>

</beans> 為了測試定義檔撰寫是否正確,以及依賴關係是否如預期的建立,您可以撰寫一個如下的測試程式:

CollectionDemo SpringDemo.java

package onlyfun.caterpillar;

import java.util.List;

import java.util.Map;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

Page 37: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�37

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

SomeBean someBean =

(SomeBean) context.getBean("someBean");

// 取得陣列型態依賴注入物件

String[] strs =

(String[]) someBean.getSomeStrArray();

Some[] somes =

(Some[]) someBean.getSomeObjArray();

for(int i = 0; i < strs.length; i++) {

System.out.println(strs[i] + ","

+ somes[i].getName());

}

// 取得 List 型態依賴注入物件

System.out.println();

List someList = (List) someBean.getSomeList();

for(int i = 0; i < someList.size(); i++) {

System.out.println(someList.get(i));

}

// 取得 Map 型態依賴注入物件

System.out.println();

Map someMap = (Map) someBean.getSomeMap();

System.out.println(someMap.get("MapTest"));

System.out.println(someMap.get("someKey1"));

}

} 執行的結果如下所示:

Page 38: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�38

圖 3.5 CollectionDemo 專案執行結果 在設定 Map注入時,<entry>標籤的設定上,原本以下的形式:

<entry key="MapTest">

<value>Hello!Justin!</value>

</entry> 還可以撰寫為:

<entry key="MapTest" value="Hello!Justin!"/> 如果 key-value分別要參考至某個 Bean實例,則可以撰寫為以下的方式:

<entry>

<key>

<ref bean="myKeyBean"/>

</key>

<ref bean="myValueBean"/>

</entry> 您還可以撰寫為以下的型式,在撰寫上更為簡潔:

<entry key-ref="myKeyBean" value-ref="myValueBean"/> 如果您所使用的 JDK是 5.0以上的版本,在定義集合物件時,也許會使用泛型(Generic)的功能來定義集合物件所存放的型態,例如:

Page 39: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�3�

public class Some {

private Map<String, Float> somes;

public void setSomes(Map<String, Float> somes) {

this.somes = somes;

}

} 則在進行依賴注入時,Spring 會嘗試為屬性作型態轉換的動作,再將之放入集合物件之中,例如:

...

<bean id="some" class="onlyfun.caterpillar.Some">

<property name="somes">

<map>

<entry key="Justin" value="99.99"/>

<entry key="momor" value="99.75"/>

</map>

</property>

</bean>

... 在這樣的設定之下,Spring會將 key的值轉換為 String,value的設定轉換為 Float,以符合 Some類別中 somes屬性的泛型宣告。 如果要注入的是 java.util.Set型態的話,則使用 <set> 標籤,一個設定的片段如下所示:

...

<set>

<value>a set element</value>

<ref bean="otherBean"/>

<ref bean="anotherBean"/>

</set>

... 若要注入的 java.util.Properties型態,則使用<props>與<prop>標籤,Bean定義檔的寫法示範如下:

Page 40: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�4�

<bean id=....>

....

<property name="someProperties">

<props>

<prop key="someProkey1">someProValue1</prop>

<prop key="someProkey2">someProValue2</prop>

</props>

</property>

</bean> 如果您的集合物件不只注入一個物件,則要考慮為集合物件設定"id"名稱,例如若是 List 物件,則要使用 org.springframework.beans.factory.

config.ListFactoryBean來定義,如下所示:

<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">

<property name="sourceList">

<list>

<value>[email protected]</value>

<value>[email protected]</value>

</list>

</property>

</bean> 如果是 Map物件,則可以如下設定:

<bean id="emails"

class="org.springframework.beans.factory.config.MapFactoryBean">

<property name="sourceMap">

<map>

<entry key="justin" value="[email protected]"/>

<entry key="momor" value="[email protected]"/>

</map>

</property>

</bean> 如果是 Set物件,則可以如下設定:

<bean id="emails"

class="org.springframework.beans.factory.config.SetFactoryBean">

Page 41: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�41

<property name="sourceSet">

<set>

<value>[email protected]</value>

<value>[email protected]</value>

</set>

</property>

</bean> 如果是 Properties型態,則可以使用 org.springframework.beans.factory.

config.PropertiesFactoryBean,例如:

<bean id="propConfiguration"

class="org.springframework.beans.

→ factory.config.PropertiesFactoryBean">

<props>

<prop key="someProkey1">someProValue1</prop>

<prop key="someProkey2">someProValue2</prop>

</props>

</bean> 或是使用"location"屬性,指定.properties檔案的位置,從中讀取 Properties資料,例如:

<bean id="businessConfig"

class="org.springframework.beans.

→ factory.config.PropertiesFactoryBean">

<property name="location"

value="classpath:onlyfun/caterpillar/config.properties"/>

</bean> 在 Spring 2.0中,還支援集合物件的合併,這與 3.1.6談到的 Bean定義之繼承類似,例如:

<bean id="parent" abstract="true" class="onlyfun.caterpillar.Some">

<property name="someProperties">

<props>

<prop key="someProkey1">someProValue1</prop>

<prop key="someProkey2">someProValue2</prop>

</props>

</property>

Page 42: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�42

</bean>

<bean id="child" parent="parent">

<property name="someProperties">

<props merge="true">

<prop key="someProkey2">otherProValue2</prop>

<prop key="someProkey3">someProValue3</prop>

</props>

</property>

</bean> 注意到"child"中的<props>設定了"merge"屬性為"true",以上的設定所取得的"child",將會繼承"parent"中的定義,重新定義"someProKey2"為"otherProValue2",並新增"someProKey3"為"someProValue3",也就是最後相當於定義以下的.properties檔案:

someProKey1=someProValue1

someProKey2=otherProValue2

someProKey3=someProValue3

3.2.5 Spring 2.0的<util>標籤 在 Spring 2.0中若使用基於 XML Schema的 XML定義檔設定,則可以加入新增的<util>標籤擴充。<util>標籤在設定 Bean 定義時更為方便省事,對於 XML 設定檔的簡化有很大的幫助,要使用<util>標籤,必須在XML中加入 util名稱空間(namespace):

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-2.0.xsd">

...

</beans>

Page 43: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�43

首先來介紹<util>標籤於集合物件設定上的簡化,可以分別使用<util:list>、<util:map>、<util:set>、<util:properties>等標籤,來取代先前所介紹的 ListFactoryBean、 MapFactoryBean、 SetFactoryBean 與PropertiesFactoryBean。這邊先以 CollectionDemo 專案為例,可以將其XML設定檔更換為以下的內容:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-2.0.xsd">

<bean id="some1" class="onlyfun.caterpillar.Some">

<property name="name" value="Justin"/>

</bean>

<bean id="some2" class="onlyfun.caterpillar.Some">

<property name="name" value="momor"/>

</bean>

<util:list id="strArray">

<value>Hello</value>

<value>Welcome</value>

</util:list>

<util:list id="objArray">

<ref bean="some1"/>

<ref bean="some2"/>

</util:list>

<util:list id="list" list-class="java.util.ArrayList">

<value>ListTest</value>

<ref bean="some1"/>

<ref bean="some2"/>

</util:list>

Page 44: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�44

<util:map id="map" map-class="java.util.HashMap">

<entry key="MapTest" value="Hello!Justin!"/>

<entry key="someKey1" value-ref="some1"/>

</util:map>

<bean id="someBean" class="onlyfun.caterpillar.SomeBean">

<property name="someStrArray" ref="strArray"/>

<property name="someObjArray" ref="objArray"/>

<property name="someList" ref="list"/>

<property name="someMap" ref="map"/>

</bean>

</beans> 其中"list-class"、"map-class"屬性可以指定所要使用的集合物件,若不指定,則由容器自行決定實作的容器物件。以下是<util:properties>標籤的使用範例:

<util:properties id="businessConfig"

location="classpath:onlyfun/caterpillar/cpnfig.properties"/> 以下是<util:set>的使用範例,同樣的,"set-class"可用來指定集合物件所使用的型態:

<util:set id="emails" set-class="java.util.TreeSet">

<value>[email protected]</value>

<value>[email protected]</value>

</util:set> 除了這邊所介紹的<util>標籤之外,還有<util:constant>可用來設定靜態資料成員(static field),而免於設定 org.springframework.beans.factory.

config.FieldRetrievingFactoryBean,例如:

<bean id="..." class="...">

<property name="someProperty">

<util:constant

static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>

</property>

</bean>

Page 45: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�4�

還可以使用<util:property-path>標籤,可以為某個 Bean的屬性成員設定"id"名稱,使之可於容器管理之中,以免於設定 org.springframework.

beans.factory.config.PropertyPathFactoryBean,例如:

<bean id="someBean" class="onlyfun.caterpillar.SomeBean">

<property name="name" value="guest"/>

</bean>

<!—id 為 name 的 Bean,其值將會是 someBean.name-->

<util:property-path id="name" path="someBean.name"/>

3.2.6 Lookup Method Injection 假設現在要設計一個 Singleton的 MessageManager,當呼叫 display()方法時,會取得一個系統新建立的 Message物件並加以顯示,例如:

LookupMethodInjectionDemo MessageManager.java

package onlyfun.caterpillar;

public abstract class MessageManager {

public void display() {

Message message = createMessage();

System.out.println(message);

}

protected abstract Message createMessage();

}

Message 收集了系統當時的相關資訊,在這邊的範例則只簡單的收集系統的時間資訊:

Page 46: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�46

LookupMethodInjectionDemo Message.java

package onlyfun.caterpillar;

import java.util.Date;

public class Message {

private String sysMessage;

public Message() {

sysMessage = "系統資訊:" + new Date().toString();

}

public String toString() {

return sysMessage;

}

} 在這邊不想撰寫程式直接實作 createMessage()方法,使之 new 一個Message 物件並傳回,而希望透過 Spring 來產生並注入 Message 物件的話,以獲得在抽換 Message的彈性。單純的在 XML設定檔中,將 Message的"scope"屬性設定"prototype"是行不通的,因為這麼設定,只有在透過BeanFactory或 ApplicationContext的 getBean()來取得 Message時,才會重新實例化一個 Message。 這時可以使用 Spring 的 Lookup Method Injection,使用<lookup-

method>標籤,可指定使用某個 Bean的方法,產生新的物件並進行注入,例如在 XML設定檔中,可以這麼撰寫:

LookupMethodInjectionDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

Page 47: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�47

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="sysMessage"

class="onlyfun.caterpillar.Message" scope="prototype"/>

<bean id="messageManager"

class="onlyfun.caterpillar.MessageManager">

<lookup-method name="createMessage" bean="sysMessage"/>

</bean>

</beans> 這麼設定之下,Spring將使用 CGLIB產生一個 MessageManager的子類別實作(所以要將 CGLIB 的.jar 加入 Classpath 之中),並且在每次呼叫到 createMessage()方法時,建立一個 Message物件並傳回,可以使用一個簡單的程式來示範執行結果:

LookupMethodInjectionDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.

ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

MessageManager messageManager =

(MessageManager) context.getBean("messageManager");

try {

messageManager.display();

Thread.sleep(1000);

messageManager.display();

Thread.sleep(1000);

messageManager.display();

} catch (InterruptedException e) {

e.printStackTrace();

}

Page 48: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�48

}

} 程式中故意在每次呼叫 display()之間暫停 1000 毫秒,區隔系統的時間資訊,以觀察 Message物件是否確實重新產生,可以從執行的結果中,看到確實每次都產生新的 Message物件:

圖 3.6 LookupMethodInjectionDemo 專案執行結果

Lookup Method Injection是 Spring所提供的 Method Injection解決方案之一,從實際的例子中可以看到,它可以指定方法 createMessage(),由Spring提供實作,將指定的 Message物件注入 MessageManager之中。

Spring中還提供一個更不常用的 Arbitrary method replacement,可以更進一步的取代某個物件的方法實作,而不用修改原來的物件。若有興趣,可以直接參考 Spring參考文件當中,3.3.8.2. Arbitrary method replacement的相關範例與說明。

Page 49: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�4�

3.3 Bean進階管理 理想上對於應用程式組件來說,最好的情況是這些組件不需要意識到Spring容器的存在,具體來說就是不使用到 Spring的相關 API,然而您可以為應用程式組件撰寫一些 Spring 服務程式,讓這些程式得知有關於Spring容器的一些訊息,以獲得 Spring所提供的一些功能。另一方面,您可能必須讓容器對應用程式組件進行一些額外處理,這個小節提供一些進階的 Bean設定與管理的方式。

3.3.1 非 XML定義檔的組態方式 在先前所見到的每個 Spring示範程式都使用 XML作為定義檔,XML檔案的階層格式適用於組態設定,也因此許多的開放原始碼專案(Open

source project)都將 XML 作為預設的組態定義方式,但通常也會提供非XML 定義檔的方式,例如提供屬性檔案 .properties 的設定方式,Spring也可以使用屬性檔案 .properties定義 Bean。 舉個實際的例子來說,假設您定義了一個 HelloBean類別:

PropertiesDemo HelloBean.java

package onlyfun.caterpillar;

public class HelloBean {

private String helloWord;

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

return helloWord;

}

Page 50: Spring 2.0 技術手冊第三章 - IoC 容器

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

3���

} 在這邊改撰寫一個 beans-config.properties來定義 Bean與依賴注入等內容:

PropertiesDemo beans-config.properties

helloBean.(class)=onlyfun.caterpillar.HelloBean

helloBean.helloWord=Welcome 屬性檔中 "helloBean" 名稱即是 Bean的別名設定,.(class)用於指定類別來源,有括號表示 Bean 定義檔所需的屬性設定,例如還有如.(abstract)、.(parent)、.(lazy-init)、.(singleton)等可以設定,至於其它的屬性設定方式,例如".helloWord"即為 Bean的屬性名稱,如果要參考已存在的 Bean,則使用".helloWorld(ref)"。可以使用 org.springframework.

beans.factory.support.PropertiesBeanDefinitionReader 來讀取屬性檔,讀取 .properties檔案中的定義之示範程式如下:

PropertiesDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.beans.factory.BeanFactory;

import org.springframework.beans.factory.

support.BeanDefinitionRegistry;

import org.springframework.beans.factory.

support.DefaultListableBeanFactory;

import org.springframework.beans.factory.

support.PropertiesBeanDefinitionReader;

import org.springframework.core.io.ClassPathResource;

public class SpringDemo {

public static void main(String[] args) {

BeanDefinitionRegistry reg =

new DefaultListableBeanFactory();

PropertiesBeanDefinitionReader reader =

new PropertiesBeanDefinitionReader(reg);

Page 51: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3��1

reader.loadBeanDefinitions(

new ClassPathResource(

"beans-config.properties"));

BeanFactory factory = (BeanFactory) reg;

HelloBean hello =

(HelloBean) factory.getBean("helloBean");

System.out.println(hello.getHelloWord());

}

} 執行結果會顯示 "Welcome" 文字。 除了透過 XML或 .properties檔案,也可以在程式中直接撰寫程式,以定義 Bean 與 Bean 之間的依賴關係,透過 org.springframework.beans.Mutable-

PropertyValues 設置屬性,將屬性與 Bean 的類別設定給 org.springframework.

beans.factory.support.RootBeanDefinition,並向 org.springframework.beans.fac-

tory.support.BeanDefinitionRegistry註冊。 不使用任何的檔案來定義 Bean的好處是,客戶端與定義檔是隔離的,它們無法接觸定義檔的內容,另一方面又可以使用到 Spring所提供的容器等相關功能。 直接來看個例子,可以將 PropertiesDemo的 SpringDemo類別改寫如下:

package onlyfun.caterpillar;

import org.springframework.beans.factory.

support.BeanDefinitionRegistry;

import org.springframework.beans.factory.

support.DefaultListableBeanFactory;

import org.springframework.beans.factory.

support.RootBeanDefinition;

import org.springframework.beans.factory.BeanFactory;

import org.springframework.beans.MutablePropertyValues;

Page 52: Spring 2.0 技術手冊第三章 - IoC 容器

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

3��2

public class SpringDemo {

public static void main(String[] args) {

// 設置屬性

MutablePropertyValues properties =

new MutablePropertyValues();

properties.addPropertyValue(

"helloWord", "Hello!Justin!");

// 設置 Bean 定義

RootBeanDefinition definition =

new RootBeanDefinition(

HelloBean.class, properties);

// 註冊 Bean 定義與 Bean 別名

BeanDefinitionRegistry reg =

new DefaultListableBeanFactory();

reg.registerBeanDefinition("helloBean", definition);

BeanFactory factory = (BeanFactory) reg;

HelloBean hello =

(HelloBean) factory.getBean("helloBean");

System.out.println(hello.getHelloWord());

}

} 執行結果會顯示 "Hello!Justin!" 的文字。

3.3.2 Aware相關介面 對於應用程式組件來說,最佳的情況是它根本不知道自己是被 Spring容器所管理,應用程式可以作自己該從事的工作或商務邏輯,而不會與Spring容器或相關 API產生耦合關係。 然而有時候,為了善用 Spring所提供的一些功能,必須讓 Bean知道Spring 容器管理它的一些細節,例如必須讓 Bean 知道自己在容器中是以哪個別名被管理的,或者是讓它知道 BeanFactory、ApplicationContext的存在,也就是讓 Bean可以取得目前運行中的 BeanFactory或 Application-

Page 53: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3��3

Context的實例,例如若 Bean可以意識到 ApplicationContext的存在的話,在 Bean 的某些動作發生時,可以進行事件發佈,並讓該事件感興趣的接受者來處理。

Spring中提供一些 Aware相關介面,像是 org.springframework.beans.

factory.BeanNameAware、org.springframework.beans.factory.BeanFactory-

Aware、org.springframework.context.ApplicationContextAware 等,實作這些 Aware介面的 Bean類別在被初始之後,可以取得一些 Spring所提供的資源或使用某些功能。 例如實作 BeanNameAware 介面的 Bean 類別,在依賴關係設定完成後、初始化方法之前(例如 InitializingBean的 afterPropertiesSet() 方法或自定義的 init 方法),會將 Bean 於定義檔中的名稱透過 setBeanName() 方法設定給 Bean,BeanNameAware介面於 Spring中的定義如下:

package org.springframework.beans.factory;

public interface BeanNameAware {

public void setBeanName(String name);

} 實作 BeanFactoryAware介面的 Bean類別,在依賴關係設定完成後、初始化方法之前,Spring容器將會注入 BeanFactory的實例,BeanFactory-

Aware介面於 Spring中的定義如下:

package org.springframework.beans.factory;

public interface BeanFactoryAware {

public void setBeanFactory(BeanFactory beanFactory)

throws BeansException;

}

Page 54: Spring 2.0 技術手冊第三章 - IoC 容器

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

3��4

而實作 ApplicationContextAware介面的 Bean 類別,在 Bean類別被初始後,將會被注入 ApplicationContext的實例,ApplicationContextAware介面於 Spring中的定義如下:

package org.springframework.context;

public interface ApplicationContextAware {

public void setApplicationContext(

ApplicationContext context) throws BeansException;

} 另外也可以實作 org.springframework.context.ResourceLoaderAware介面,可以讓 Bean取得 ResourceLoader實例,並進一步取得相關的資源檔案。 當然的,一旦實作以上所提供的相關介面,則應用程式組件就會因使用到 Spring 的相關 API,而與 Spring 產生耦合關係,所以像這樣用來取得 Spring 資源的 Bean,設計上應該算是 Spring框架服務的一部份,是應用程式組件與 Spring 框架的橋樑。

3.3.3 BeanPostProcessor 在 Bean的依賴關係由 Spring容器建立並設定完成之後,您還有機會自訂一些對 Bean的修正動作來修正相關的屬性,方法是讓 Bean類別實作org.springframework.beans.factory.config.BeanPostProcessor介面,該介面於 Spring中的定義如下:

package org.springframework.beans.factory.config;

public interface BeanPostProcessor {

public Object postProcessBeforeInitialization(

Object bean, String name) throws BeansException;

public Object postProcessAfterInitialization(

Page 55: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3���

Object bean, String name) throws BeansException;

}

postProcessBeforeInitialization() 方法會在 Bean 類別被初始化之前(例如 InitializingBean的 afterPropertiesSet() 方法或自定義的 init方法)被執行,而 postProcessAfterInitialization() 方法會在 Bean 類別被初始化之後立即被執行。 舉個例子來說,您可以實作一個英文字母轉大寫修正器類別,作用是對於 String 型態的依賴注入,無論在定義檔中撰寫時是大寫或小寫,在Bean的依賴注入設定完成之後,使用英文字母轉大寫修正器將所有注入的String 改為大寫,英文字母轉大寫修正器 UppercaseModifier 類別撰寫如下:

BeanPostProcessorDemo UppercaseModifier.java

package onlyfun.caterpillar;

import java.lang.reflect.Field;

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.

config.BeanPostProcessor;

public class UppercaseModifier

implements BeanPostProcessor {

public Object postProcessBeforeInitialization(

Object bean, String name) throws BeansException {

// 取得 Bean 的 Field 成員

Field[] fields =

bean.getClass().getDeclaredFields();

for(int i = 0; i < fields.length; i++) {

// 針對 String 型態的 Field 成員加以修正

if(fields[i].getType().equals(String.class)) {

fields[i].setAccessible(true);

Page 56: Spring 2.0 技術手冊第三章 - IoC 容器

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

3��6

try {

String original =

(String) fields[i].get(bean);

// 改為大寫

fields[i].set(bean, original.toUpperCase());

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

return bean;

}

public Object postProcessAfterInitialization(

Object bean, String name) throws BeansException {

return bean;

}

} 配合這個類別的設計,來示範一下如何應用,假設您定義了一個 Hello-

Bean類別:

BeanPostProcessorDemo HelloBean.java

package onlyfun.caterpillar;

public class HelloBean {

private String helloWord;

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

return helloWord;

}

}

Page 57: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3��7

在 Bean定義檔的撰寫上,如下所示:

BeanPostProcessorDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="uppercaseModifier"

class="onlyfun.caterpillar.UppercaseModifier"/>

<bean id="helloBean" class="onlyfun.caterpillar.HelloBean">

<property name="helloWord" value="Hello"/>

</bean>

</beans> 注意到在"helloWord"屬性上設定的 "Hello" 英文字母是有大寫也有小寫的, ApplicationContext 會自動偵測是否在定義檔中定義了實作BeanPostProcessor介面的類別,Spring容器會在每一個 Bean被初始化之前、之後分別執行 uppercaseModifier 的 postProcessBeforeInitialization() 方法與 postProcessAfterInitialization() 方法,以對 Bean 進行指定的相關修正,可以實際來看看以下的測試程式:

BeanPostProcessorDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

Page 58: Spring 2.0 技術手冊第三章 - IoC 容器

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

3��8

"beans-config.xml");

HelloBean hello =

(HelloBean) context.getBean("helloBean");

System.out.println(hello.getHelloWord());

}

} 雖然在 Bean 定義檔中, "helloBean"的 "helloWord"屬性上設定的

"Hello" 英文字母有大寫也有小寫,但執行結果中將會顯示 "HELLO" 的文字,也就是所有的英文字母都被轉換為大寫字母。

3.3.4 BeanFactoryPostProcessor 在 BeanFactory載入 Bean定義檔的所有內容,但還沒正式產生 Bean實例之前,可以對該 BeanFactory進行一些處理,方法是讓 Bean類別實作org.springframework.beans.factory.config.BeanFactoryPostProcessor介面,該介面於 Spring中的定義如下:

package org.springframework.beans.factory.config;

public interface BeanFactoryPostProcessor {

public void postProcessBeanFactory(

ConfigurableListableBeanFactory beanFactory)

throws BeansException;

} 假設有一個 SomClass實作 BeanFactoryPostProcessor介面,則可以在Bean定義檔中定義它:

....

<beans>

<bean id="someClass"

class="onlyfun.caterpillar.SomeClass"/>

....

</beans>

....

Page 59: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3���

使用 ApplicationContext 時,若 Bean 定義檔中有定義了實作 Bean-

FactoryPostProcessor介面的類別,則 ApplicationContext會自動使用這些類別的實例。 在 Spring中提供有幾個 BeanFactoryPostProcessor介面的實作類別,像是 org.springframework.beans.factory.config.PropertyPlaceholderCon-

figurer、org.springframework.beans.factory.config.PropertyOverrideCon-

figurer、org.springframework.beans.factory.config.CustomEditorConfigurer,這幾個類別的運用方式將在接下來一個一個為您介紹。

3.3.5 PropertyPlaceholderConfigurer

Spring 提供了一個 BeanFactoryPostProcessor 介面的實作類別:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer。藉由這個類別,可以將一些組態設定資訊移出至一個或多個的 .properties檔案中,如此的安排可以讓 XML定義檔負責系統相關設定,而 .properties檔可以作為讓客戶根據實際應用時的需求,自定義一些相關的參數。 來看一個 Bean定義檔的實際例子,可以將之前的 BeanPostProcessor-

Demo專案中之 beans-config.xml修改為以下的內容:

PropertyPlaceholderConfigurerDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="configBean"

class="org.springframework.beans.factory.

→ config.PropertyPlaceholderConfigurer">

<property name="location" value="hello.properties"/>

Page 60: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�6�

</bean>

<bean id="helloBean" class="onlyfun.caterpillar.HelloBean">

<property name="helloWord"

value="${onlyfun.caterpillar.helloWord}"/>

</bean>

</beans> 假設在"helloBean"中有許多依賴注入的屬性,這些都是比較不常變動的屬性,而其中"helloWord"會經常變動,可以撰寫一個 hello.properties檔案設定這個屬性,而 hello.properties 檔案的指定被設定在"configBean"的 "location" 屬性中,hello.properties的內容撰寫如下:

PropertyPlaceholderConfigurerDemo hello.properties

onlyfun.caterpillar.helloWord=Welcome 在"helloBean"的"helloWord"屬性中,${onlyfun.caterpillar.helloWord} 將會被 hello.properties的 onlyfun.caterpillar.helloWord之設定值所取代,所以最後的結果會顯示 "Welcome" 的文字。 如果有多個 .properties 檔案,則可以透過 "locations" 屬性來設定, 例如:

...

<beans>

<bean id="configBean"

class="org.springframework.beans.factory.

→ config.PropertyPlaceholderConfigurer">

<property name="locations">

<list>

<value>hello.properties</value>

<value>welcome.properties</value>

<value>other.properties</value>

</list>

Page 61: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�61

</property>

</bean>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="helloWord"

value="${onlyfun.caterpillar.helloWord}"/>

</bean>

</beans>

3.3.6 PropertyOverrideConfigurer

Spring 框架提供了一個 BeanFactoryPostProcessor 介面的實作類別:org.springframework.beans.factory.config.PropertyOverrideConfigurer。藉由這個類別,您可以在 .properties 檔案中設定一些優先的屬性設定,當 .properties檔案中的某個設定與 XML中的某個設定有重複時,則 XML中的設定會被覆蓋,也就是以 .properties檔案中的設定值為主。 舉個實際的程式作為示範,可以修改之前的 PropertyPlaceholder-

ConfigurerDemo專案中的 beans-config.xml檔案如下:

PropertyOverrideConfigurerDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="configBean"

class="org.springframework.beans.factory.

→ config.PropertyOverrideConfigurer">

<property name="location" value="hello.properties"/>

</bean>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="helloWord" value="Hello"/>

Page 62: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�62

</bean>

</beans> 在 hello.properties的撰寫上,假設內容如下:

PropertyOverrideConfigurerDemo hello.properties

helloBean.helloWord=Welcome

"helloBean"對應於 XML 定義檔中的某個 Bean 的 "id" 值,當中的"helloWord"設定將覆蓋 XML中的"helloWord"屬性設定,如果執行先前撰寫的 SpringDemo程式,則程式會顯示 .properties檔案中設定的 "Welcome" 文字,而不是 XML檔案中的 "Hello" 文字。 同樣的,如果有多個 .properties檔案,則可以透過 "locations" 屬性來設定,可以參考之前介紹 PropertyPlaceholderConfigurer時的設定方式。 基於篇幅的限制,這邊並沒有將修改過後完整的程式架構中所有的檔案列出來,提醒一下,如果在範例標題上提供了專案名稱,則可以在光碟中找到對應的完整範例專案,必要時可以觀看一下完整的程式需要哪些檔案以及如何撰寫。

3.3.7 CustomEditorConfigurer

Spring 框架提供了一個 BeanFactoryPostProcessor 介面的實作類別:org.springframework.beans.factory.config.CustomEditorConfigurer。這個類別可以讀取實作 java.beans.PropertyEditor 介面的類別,並依當中的實作,將字串值轉換為指定型態的物件。

Page 63: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�63

舉個例子來說,假設現在設計了兩個類別:User 類別與 HelloBean類別。

CustomEditorConfigurerDemo User.java

package onlyfun.caterpillar;

public class User {

private String name;

private int number;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getNumber() {

return number;

}

public void setNumber(int number) {

this.number = number;

}

}

CustomEditorConfigurerDemo HelloBean.java

package onlyfun.caterpillar;

public class HelloBean {

private String helloWord;

private User user;

public void setHelloWord(String helloWord) {

this.helloWord = helloWord;

}

public String getHelloWord() {

Page 64: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�64

return helloWord;

}

public User getUser() {

return user;

}

public void setUser(User user) {

this.user = user;

}

} 若依之前所介紹過的設定方式,基本上要在 Bean 定義檔中作以下的設定,以完成依賴注入:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="userBean" class="onlyfun.caterpillar.User">

<property name="name" value="Justin"/>

<property name="number" value="5858588"/>

</bean>

<bean id="helloBean" class="onlyfun.caterpillar.HelloBean">

<property name="helloWord" value="Hello!"/>

<property name="user" ref="userBean"/>

</bean>

</beans> 事實上這邊不採取以上的設定,可以在 Bean 定義檔中直接撰寫一個字串設定,然後讓 Spring自動依指定的字串轉換為指定型態的物件,您可以實作 java.beans.PropertyEditor 介面,但更方便的是繼承 java.beans.

PropertyEditorSupport。

Page 65: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�6�

PropertyEditorSupport 實作了 PropertyEditor 介面,您可以重新定義它的 setAsText() 方法,這個方法傳入一個字串值,您可以根據這個字串值內容來建立一個 User 物件,例如撰寫一個 UserEditor 類別,它繼承了PropertyEditorSupport類別:

CustomEditorConfigurerDemo UserEditor.java

package onlyfun.caterpillar;

import java.beans.PropertyEditorSupport;

public class UserEditor extends PropertyEditorSupport {

public void setAsText(String text) {

String[] strs = text.split(",");

int number = Integer.parseInt(strs[1]);

User user = new User();

user.setName(strs[0]);

user.setNumber(number);

setValue(user);

}

}

UserEditor類別會依傳入的字串以 "," 分隔設定,將之分為兩個部份的資訊,之後產生一個 User實例,再將這兩個資訊設定給 User實例上的對應屬性,為了運用這個 UserEditor 類別,您要在 Bean 定義檔中使用CustomEditorConfigurer類別,並設定它的 "customEditors" 屬性,例如:

CustomEditorConfigurerDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

Page 66: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�66

<bean id="configBean"

class="org.springframework.beans.factory.

→ config.CustomEditorConfigurer">

<property name="customEditors">

<map>

<entry key="onlyfun.caterpillar.User">

<bean id="userEditor"

class="onlyfun.caterpillar.UserEditor"/>

</entry>

</map>

</property>

</bean>

<bean id="helloBean"

class="onlyfun.caterpillar.HelloBean">

<property name="helloWord" value="Hello"/>

<property name="user" value="Justin,5858588"/>

</bean>

</beans> 只要是 User類型的屬性,現在可以使用 "Justin,5858588" 這樣的字串來設定,CustomEditorConfigurer 會載入 "customEditors" 屬性中設定的key-value,根據 key遇到哪一個型態時要使用 PropertyEditor,value設定就是指定對應的 PropertyEditor。 為了驗證設定是否正確,來簡單的撰寫一個測試程式執行,看看結果如何:

Page 67: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�67

CustomEditorConfigurerDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

HelloBean hello =

(HelloBean) context.getBean("helloBean");

System.out.println(hello.getHelloWord() + "!");

System.out.println("Name:\t"

+ hello.getUser().getName());

System.out.println("Number:\t"

+ hello.getUser().getNumber());

}

} 執行結果如下所示,您可以透過 User的實例直接取得 Bean定義檔中設定的值:

圖 3.7 CustomEditorConfigurerDemo 專案執行結果

Page 68: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�68

3.4 資源、訊息、事件 ApplicationContext除了具備如 BeanFactory基本的容器管理功能之外,並支援更多應用程式框架的特性,像是資源的取得、訊息解析、事件的處理與傳播,您可以基於 Spring容器來打造屬於自己的應用程式或框架。

3.4.1 資源的取得

Spring 提供了對資源檔案的泛型存取(Generic access),Application-

Context繼承了 org.springframework.core.io.ResourceLoader介面,您可以使用 getResource() 方法並指定資源檔案的 URL(Uniform Resource Locator)來取得一個實作 Resource介面的實例,例如:

Resource resource = context.getResource(

"classpath:admin.properties");

"classpath:" 是 Spring自訂的URL虛擬協定,這會取回一個 org.spring-

framework.core.io.ClassPathResource的實例,代表一個具體的資源檔案,在上面的程式片段中,該資源檔案是位於 Classpath 根目錄中,檔案名稱為 admin.properties,您也可以指定標準的 URL,像是 "file:" 或 "http:",例如:

Resource resource = context.getResource(

"file:c:/workspace/springtest/conf/admin.properties"); 這會回傳一個 org.springframework.core.io.FileSystemResource 的實例,或者您可以如下指定來回傳一個 ServletContextResource實例:

Resource resource = context.getResource("WEB-INF/conf/admin.properties");

Page 69: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�6�

取得一個實作 Resource 介面的實例之後,您可以使用 getFile()、getInputStream() 等方式來操作或取得資源檔案的相關資源,Resource 介面的實例只是資源檔案的一個抽象代表,指定的資源檔案實際上可能並不存在,您可以使用 exists() 方法來進行測試。

3.4.2 解析文字訊息

ApplicationContext 繼承了 org.springframework.context.MessageSource介面,您可以使用 getMessage() 的各個方法版本來取得文字訊息的資源檔案,從而實現國際化訊息的目的。 您可以簡單的透過 MessageSource 的一個實作 org.springframework.

context.support.ResourceBundleMessageSource 來取得國際化訊息,直接以實例進行說明,首先在 Bean定義檔中撰寫以下的內容:

MessageSourceDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="messageSource"

class="org.springframework.context.

→ support.ResourceBundleMessageSource">

<property name="basename" value="messages"/>

</bean>

</beans>

"basename" 屬性用來設定訊息資源的前置檔案名稱,在這邊設定是

"messages",則表示訊息資源檔的名稱可以是 messages_en_US.properties、messages_zh_TW.properties、 messages_*.properties等名稱,在這邊提供

Page 70: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�7�

兩個:messages_en_US.properties、messages_zh_TW.properties,首先messages_en_US.properties內容撰寫如下:

MessageSourceDemo messages_en_US.properties

userLogin=User {0} login at {1} 這是美式英文語系的文字訊息檔案,若使用美式英文語系設定時,將取得這個文字訊息檔案的內容並顯示之,其中 {0} 與 {1} 則是為了可以在程式執行過程中,替換實際文字而設定的佔位字元。 另外再來提供 messages_zh_TW.properties 檔案,由於直接使用 Big5中文編碼撰寫的文字檔案,在讀取時會因編碼問題而出現亂碼,所以您必須先撰寫一個 messages_zh_TW.txt檔案,內容如下:

MessageSourceDemo messages_zh_TW.txt

userLogin=使用者 {0} 於 {1} 登入 然後使用 JDK工具程式中的 native2ascii進行編碼轉換,執行指令的方式如下:

native2ascii messages_zh_TW.txt messages_zh_TW.properties 轉換之後的 messages_zh_TW.properties 檔案內容為經過編碼轉換後的 Unicode編碼,內容如下所示:

MessageSourceDemo messages_zh_TW.properties

userLogin=\u4f7f\u7528\u8005 {0} \u65bc {1} \u767b\u5165

Page 71: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�71

ResourceBundleMessageSource實際上會使用 JDK的ResourceBundle來讀取這些文字訊息文件,接下來看看如何進行語系相關設定,並看看在不同的語系之下所讀取的各是哪個文字訊息文件:

MessageSourceDemo SpringDemo.java

package onlyfun.caterpillar;

import java.util.Calendar;

import java.util.Locale;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

Object[] arguments =

new Object[] {"良葛格",

Calendar.getInstance().getTime()};

System.out.println(

context.getMessage("userLogin",

arguments,

Locale.US));

System.out.println(

context.getMessage("userLogin",

arguments,

Locale.TAIWAN));

}

} 由於專案中 beans-config.xml正好在 Classpath中,所以程式中也可使用 ClassPathXmlApplicationContext 來讀取,文字訊息文件的選擇根據,是依一個 java.util.Locale 物件來決定,在使用 getMessage() 方法取得訊

Page 72: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�72

息時可以指定一個 Locale 物件,範例程式中分別指定了 Locale.US 代表en_US語系,而 Locale.TAIWAN代表 zh_TW語系,實際執行程式的結果如下所示:

圖 3.8 MessageSourceDemo 專案執行結果

3.4.3 傾聽事件 在 Spring 應用程式執行期間,ApplicationContext 本身就會發佈一連串的事件,而所有發佈的事件都是抽象類別 org.springframework.context.

ApplicationEvent的子類別,像是:

� ContextClosedEvent 在 ApplicationContext 關閉時發佈事件。

� ContextRefreshedEvent 在 ApplicationContext 初始或 Refresh 時發佈事件。

� RequestHandledEvent 在 Web 應用程式中,當請求被處理時,ApplicationContext 會發佈此事件。 如果您對這些事件有興趣,則可以實作 org.springframework.context.

ApplicationListener介面,並在定義檔中定義實作該介面的一個 Bean實例:

package org.springframework.context;

import java.util.EventListener;

public interface ApplicationListener extends EventListener {

Page 73: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�73

void onApplicationEvent(ApplicationEvent event);

}

ApplicationContext會在 ApplicationEvent發佈時通知實作了 Applica-

tionListener的 Bean類別實例。 在 Spring提供的 API中,實作 ApplicationListener介面的類別有:

� ConsoleListener 僅在 Debug 時使用,會在文字模式下顯示記錄(Log)ApplicationContext的相關訊息。

� PerformanceMonitorListener 在 Web 應用程式中,搭配 WebApplicationContext 使用,可記錄請求的回應時間。 例如可以在 Bean定義檔中這麼撰寫:

<beans ...>

<bean id="listener"

class="org.springframework.context.

→ event.ConsoleListener"/>

....

</beans> 則運行 Spring應用程式時,ApplicationEvent 相關事件會發佈,您可以在文字模式下觀察到相關的登錄訊息。

3.4.4 事件傳播 如果打算發佈事件通知 ApplicationListener 的實例,例如在應用程式中定時發佈「心跳」事件,讓應用程式的客戶端可以知道應用程式是否還

Page 74: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�74

活著。想要發佈事件,可以使用 ApplicationContext的 publishEvent() 方法,例如:

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

....

context.publishEvent(new ContextClosedEvent(context)); 假設您在 Bean定義檔中有這麼定義:

...

<beans ...>

<bean id="listener"

class="org.springframework.context.

→ event.ConsoleListener"/>

....

</beans> 則在文字模式下會顯示事件發生當時 ApplicationContext的相關訊息:

...

org.springframework.context.event.ContextClosedEvent[source=org.spring

framework.context.support.ClassPathXmlApplicationContext: display name

[org.springframework.context.support.ClassPathXmlApplicationContext;ha

shCode=8795033]; startup date [Sat Oct 28 22:24:15 GMT+08:00 2006]; root

of context hierarchy] 接下來示範一個簡單的自定 ApplicationListener 與 ApplicationEvent的例子,首先假設您繼承 ApplicationEvent類別,自行定義一個 SomeEvent類別,如下所示:

PublishEventDemo SomeEvent.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationEvent;

Page 75: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�7�

public class SomeEvent extends ApplicationEvent {

public SomeEvent(Object source) {

super(source);

}

} 在建構 SomeEvent時,您傳遞一個事件來源物件給它,接著您希望自定義一個 CustomListener,在 SomeEvent被發佈時,可以接收到並處理該事件,CustomListener類別的定義如下所示:

PublishEventDemo CustomListener.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationEvent;

import org.springframework.context.ApplicationListener;

public class CustomListener implements ApplicationListener {

public void onApplicationEvent(ApplicationEvent event) {

if(event instanceof SomeEvent) {

System.out.println(event.getSource());

}

}

}

CustomListener 類別實作了 ApplicationListener 介面,在 onAppli-

cationEvent() 方法中,先判斷事件是不是 SomeEvent,如果是的話再將事件來源顯示在文字模式下。 在 Bean定義檔的撰寫中,只要簡單的定義一下 CustomListner即可,例如:

PublishEventDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

Page 76: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�76

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="listener"

class="onlyfun.caterpillar.CustomListener"/>

</beans> 在這邊再撰寫一個簡單的示範程式,直接發佈一個 SomeEvent 事件,看看 CustomListener是不是可以接收到該事件:

PublishEventDemo SpringDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

context.publishEvent(new SomeEvent(SpringDemo.class));

}

} 執行的結果如下圖所示:

圖 3.9 PublishEventemo 專案執行結果

Page 77: Spring 2.0 技術手冊第三章 - IoC 容器

Chapter 3 IoC 容器

3�77

3.5 接下來的主題 認識 Spring核心容器是使用 Spring的第一步,除了獲得容器管理功能之外,在設定上一致的設定方式,使得 Spring在使用上相當的簡易。另一個 Spring的核心特色,也是所有子框架基礎的就是 Spring的 AOP支援,這是 Spring的基礎,也是 Spring另一個特色,在下一個章節中,將帶領您一步一步認識 AOP,以及如何使用 Spring來實現 AOP。

圖 3.10 輕量級的 Spring 容器

Page 78: Spring 2.0 技術手冊第三章 - IoC 容器

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

3�78