?
This document uses PHP Chinese website manual Release
beans
包提供了以編程的方式管理和操控bean的基本功能,而context
包下的ApplicationContext
以一種更加面向框架的方式增強(qiáng)了BeanFactory
的功能。多數(shù)用戶可以采用聲明的方式來(lái)使用ApplicationContext
,甚至不用手動(dòng)創(chuàng)建它,而通過(guò)ContextLoader
這樣的支持類(lèi),把它作為J2EE web應(yīng)用的一部分自動(dòng)啟動(dòng)。當(dāng)然,我們?nèi)匀豢梢圆捎镁幊痰姆绞絼?chuàng)建一個(gè)ApplicationContext。
context包的核心是ApplicationContext
接口。它由BeanFactory
接口派生而來(lái),因而提供了BeanFactory
所有的功能。為了以一種更向面向框架的方式工作以及對(duì)上下文進(jìn)行分層和實(shí)現(xiàn)繼承,context包還提供了以下的功能:
MessageSource
, 提供國(guó)際化的消息訪問(wèn)
資源訪問(wèn),如URL和文件
事件傳播,實(shí)現(xiàn)了ApplicationListener
接口的bean
載入多個(gè)(有繼承關(guān)系)上下文 ,使得每一個(gè)上下文都專(zhuān)注于一個(gè)特定的層次,比如應(yīng)用的web層
簡(jiǎn)單的說(shuō):除非你有更好的理由,否則盡量使用ApplicationContext
,下面是對(duì)于哪些"為什么"等等更深入的建議
ApplicationContext
包含BeanFactory
的所有功能。通常建議比BeanFactory
優(yōu)先,除非有一些限制的場(chǎng)合如字節(jié)長(zhǎng)度對(duì)內(nèi)存有很大的影響時(shí)(Applet
)。然后,絕大多數(shù)"典型的"企業(yè)應(yīng)用和系統(tǒng),ApplicationContext
就是你需要使用的。Spring2.0及以上版本,大量使用了link linkend="beans-factory-extension-bpp">BeanPostProcessor
擴(kuò)展(以便應(yīng)用代理等功能),如果你選擇BeanFactory
則無(wú)法使用相當(dāng)多的支持功能,如事務(wù)和AOP,這可能會(huì)導(dǎo)致混亂,因?yàn)榕渲貌](méi)有錯(cuò)誤。
下面的功能矩陣列出了BeanFactory
提供的功能和ApplicationContext
提供的功能(包括其實(shí)現(xiàn))。(下一節(jié)更深度的描述了ApplicationContext
比BeanFactory
更強(qiáng)的功能。)
表?3.5.?Feature Matrix特性表
特性 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 實(shí)例化/裝配 |
Yes |
Yes |
自動(dòng)
|
No |
Yes |
自動(dòng)
|
No |
Yes |
便捷的
|
No |
Yes |
|
No |
Yes |
ApplicationContext
接口擴(kuò)展了MessageSource
接口,因而提供了消息處理的功能(i18n或者國(guó)際化)。與HierarchicalMessageSource
一起使用,它還能夠處理嵌套的消息,這些是Spring提供的處理消息的基本接口。讓我們快速瀏覽一下它所定義的方法:
String getMessage(String code, Object[] args, String default, Locale loc):用來(lái)從
MessageSource
獲取消息的基本方法。如果在指定的locale中沒(méi)有找到消息,則使用默認(rèn)的消息。args中的參數(shù)將使用標(biāo)準(zhǔn)類(lèi)庫(kù)中的MessageFormat
來(lái)作消息中替換值。
String getMessage(String code, Object[] args, Locale loc):本質(zhì)上和上一個(gè)方法相同,其區(qū)別在:沒(méi)有指定默認(rèn)值,如果沒(méi)找到消息,會(huì)拋出一個(gè)
NoSuchMessageException
異常。
String getMessage(MessageSourceResolvable resolvable, Locale locale)
:上面方法中所使用的屬性都封裝到一個(gè)MessageSourceResolvable
實(shí)現(xiàn)中,而本方法可以指定MessageSourceResolvable
實(shí)現(xiàn)。
當(dāng)一個(gè)ApplicationContext
被加載時(shí),它會(huì)自動(dòng)在context中查找已定義為MessageSource
類(lèi)型的bean。此bean的名稱(chēng)須為messageSource
。如果找到,那么所有對(duì)上述方法的調(diào)用將被委托給該bean。否則ApplicationContext
會(huì)在其父類(lèi)中查找是否含有同名的bean。如果有,就把它作為MessageSource
。如果它最終沒(méi)有找到任何的消息源,一個(gè)空的StaticMessageSource
將會(huì)被實(shí)例化,使它能夠接受上述方法的調(diào)用。
Spring目前提供了兩個(gè)MessageSource
的實(shí)現(xiàn):ResourceBundleMessageSource
和StaticMessageSource
。它們都繼承NestingMessageSource
以便能夠處理嵌套的消息。StaticMessageSource
很少被使用,但能以編程的方式向消息源添加消息。ResourceBundleMessageSource
會(huì)用得更多一些,為此提供了一下示例:
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans>
這段配置假定在你的classpath中有三個(gè)資源文件(resource bundle),它們是format
, exceptions
和windows
。通過(guò)ResourceBundle,使用JDK中解析消息的標(biāo)準(zhǔn)方式,來(lái)處理任何解析消息的請(qǐng)求。出于示例的目的,假定上面的兩個(gè)資源文件的內(nèi)容為…
# in 'format.properties'
message=Alligators rock!
# in 'exceptions.properties'
argument.required=The '{0}' argument is required.
下面是測(cè)試代碼。因?yàn)?code class="classname">ApplicationContext實(shí)現(xiàn)也都實(shí)現(xiàn)了MessageSource
接口,所以能被轉(zhuǎn)型為MessageSource
接口
public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("message", null, "Default", null); System.out.println(message); }
上述程序的輸出結(jié)果將會(huì)是...
Alligators rock!
總而言之,我們?cè)?code class="literal">'beans.xml'的文件中(在classpath根目錄下)定義了一個(gè)messageSource
bean,通過(guò)它的basenames
屬性引用多個(gè)資源文件;而basenames
屬性值由list元素所指定的三個(gè)值傳入,它們以文件的形式存在并被放置在classpath的根目錄下(分別為format.properties
,exceptions.properties
和windows.properties
)。
再分析個(gè)例子,這次我們將著眼于傳遞參數(shù)給查找的消息,這些參數(shù)將被轉(zhuǎn)換為字符串并插入到已查找到的消息中的占位符(譯注:資源文件中花括號(hào)里的數(shù)字即為占位符)。
<beans> <!-- thisMessageSource
is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="baseName" value="WEB-INF/test-messages"/> </bean> <!-- let's inject the aboveMessageSource
into this POJO --> <bean id="example" class="com.foo.Example"> <property name="messages" ref="messageSource"/> </bean> </beans>
public class Example { private MessageSource messages; public void setMessages(MessageSource messages) { this.messages = messages; } public void execute() { String message = this.messages.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message); } }
調(diào)用execute()
方法的輸出結(jié)果是...
The 'userDao' argument is required.
對(duì)于國(guó)際化(i18n),Spring中不同的MessageResource
實(shí)現(xiàn)與JDK標(biāo)準(zhǔn)ResourceBundle中的locale解析規(guī)則一樣。比如在上面例子中定義的messageSource
bean,如果你想解析British (en-GB) locale的消息,那么需要?jiǎng)?chuàng)建format_en_GB.properties
,exceptions_en_GB.properties
和windows_en_GB.properties
三個(gè)資源文件。
Locale解析通常由應(yīng)用程序根據(jù)運(yùn)行環(huán)境來(lái)指定。出于示例的目的,我們對(duì)將要處理的(British)消息手工指定locale參數(shù)值。
# in 'exceptions_en_GB.properties'
argument.required=Ebagum lad, the '{0}' argument is required, I say, required.
public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); }
上述程序運(yùn)行時(shí)的輸出結(jié)果是...
Ebagum lad, the 'userDao' argument is required, I say, required.
MessageSourceAware
接口還能用于獲取任何已定義的MessageSource
引用。任何實(shí)現(xiàn)了MessageSourceAware
接口的bean將在創(chuàng)建和配置的時(shí)候與MessageSource
一同被注入。
ApplicationContext
中的事件處理是通過(guò)ApplicationEvent
類(lèi)和ApplicationListener
接口來(lái)提供的。如果在上下文中部署一個(gè)實(shí)現(xiàn)了ApplicationListener
接口的bean,那么每當(dāng)一個(gè)ApplicationEvent
發(fā)布到ApplicationContext
時(shí),這個(gè)bean就得到通知。實(shí)質(zhì)上,這是標(biāo)準(zhǔn)的Observer設(shè)計(jì)模式。Spring提供了三個(gè)標(biāo)準(zhǔn)事件:
表?3.6.?內(nèi)置事件
事件 | 解釋 |
---|---|
ContextRefreshedEvent |
當(dāng)ApplicationContext 初始化或刷新時(shí)發(fā)送的事件。如使用ConfigurableApplicationContext 接口的refresh() 方法。這里的初始化意味著:所有的bean被裝載,后處理bean被檢測(cè)和激活,singleton被預(yù)實(shí)例化,以及ApplicationContext 已就緒可用。刷新在context關(guān)閉會(huì)觸發(fā)多次。選擇ApplicationContext 可以提供“熱”刷新的功能(如:XmlWebApplicationContext 可以但是GenericApplicationContext 則不可以.)
|
ContextStartedEvent |
當(dāng)ApplicationContext 啟動(dòng)時(shí)發(fā)送的事件,使用ConfigurableApplicationContext 接口的start() 方法。這里"啟動(dòng)"意味著生命周期 beans將獲得一個(gè)確實(shí)的啟動(dòng)信號(hào)。這經(jīng)常使用在確實(shí)停止后重新啟動(dòng)的場(chǎng)合,但也可以用在啟動(dòng)一個(gè)沒(méi)有被配置為自動(dòng)啟動(dòng)的組件中(如:在完成初始化后還沒(méi)有啟動(dòng))。
|
ContextStoppedEvent |
當(dāng)使用ConfigurableApplicationContext 接口的stop() 方法使ApplicationContext 停止時(shí)候發(fā)送的事件。這里"停止"意味著生命周期 beans將獲得一個(gè)確實(shí)的停止信號(hào). 停止的context可以通過(guò)調(diào)用start() 來(lái)重新啟動(dòng)。
|
ContextClosedEvent |
當(dāng)使用ConfigurableApplicationContext 接口close() 方法使ApplicationContext 關(guān)閉時(shí)候發(fā)送的事件。 這里關(guān)閉意味著所有的singleton bean都被銷(xiāo)毀。關(guān)閉的context不能刷新和重新啟動(dòng)。 |
RequestHandledEvent |
web特性的事件通告所有的bean有一個(gè)http request(將在request結(jié)束后才會(huì)發(fā)送)。注意這種事件只兼容于使用SpringDispatcherServlet 兼容的web應(yīng)用。
|
只要在ApplicationContext
調(diào)用publishEvent()
方法可以很方便的實(shí)現(xiàn)自定義事件,將一個(gè)實(shí)現(xiàn)了ApplicationEvent
的自定義事件類(lèi)作為參數(shù)就可以了。事件監(jiān)聽(tīng)器同步的接收事件。這意味著publishEvent()
方法將被阻塞,直到所有的監(jiān)聽(tīng)器都處理完事件(可以通過(guò)一個(gè)ApplicationEventMulticaster
的實(shí)現(xiàn)提供可選的事件發(fā)送策略)。此外,如果事務(wù)context可用,監(jiān)聽(tīng)器會(huì)接收到一含有發(fā)送者事務(wù)context的事件。
看一個(gè)例子,首先是ApplicationContext
:
<bean id="emailer" class="example.EmailBean"> <property name="blackList"> <list> <value>black@list.org</value> <value>white@list.org</value> <value>john@doe.org</value> </list> </property> </bean> <bean id="blackListListener" class="example.BlackListNotifier"> <property name="notificationAddress" value="spam@list.org"/> </bean>
再看一下實(shí)際的類(lèi):
public class EmailBean implements ApplicationContextAware {
private List blackList;
private ApplicationContext ctx;
public void setBlackList(List blackList) {
this.blackList = blackList;
}
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(address, text);
ctx.publishEvent(event);
return;
}
// send email...
}
}
public class BlackListNotifier implements ApplicationListener {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof BlackListEvent) {
// notify appropriate person...
}
}
}
當(dāng)然,這個(gè)例子可以使用更好的方法實(shí)現(xiàn)(如采用AOP特性) ,但應(yīng)該足以說(shuō)明事件的基本機(jī)制。
為了更好的使用和理解應(yīng)用上下文,通常用戶應(yīng)當(dāng)對(duì)Spring的Resource
有所了解,詳見(jiàn)第?4?章 資源
應(yīng)用上下文同時(shí)也是個(gè)資源加載器(ResourceLoader),能被用來(lái)加載多個(gè)Resource
。一個(gè)Resource
實(shí)質(zhì)上可以當(dāng)成一個(gè)java.net.URL
,可被用來(lái)從大多數(shù)位置以透明的方式獲取底層的資源,包括從classpath、文件系統(tǒng)位置、任何以標(biāo)準(zhǔn)URL描述的位置以及其它一些變種。如果資源位置串是一個(gè)沒(méi)有任何前綴的簡(jiǎn)單路徑,這些資源來(lái)自何處取決于實(shí)際應(yīng)用上下文的類(lèi)型。
為了讓bean能訪問(wèn)靜態(tài)資源,可以象其它屬性一樣注入Resource。被注入的Resource
屬性值可以是簡(jiǎn)單的路徑字符串,ApplicationContext會(huì)使用已注冊(cè)的PropertyEditor
,來(lái)將字符串轉(zhuǎn)換為實(shí)際的Resource
對(duì)象。
ApplicationContext
構(gòu)造器的路徑就是實(shí)際的資源串,根據(jù)不同的上下文實(shí)現(xiàn),字符串可視為不同的形式(例如:ClassPathXmlApplicationContext
會(huì)把路徑字符串看作一個(gè)classpath路徑)。然而,它也可以使用特定的前綴來(lái)強(qiáng)制地從classpath或URL加載bean定義文件,而不管實(shí)際的上下文類(lèi)型。
與BeanFactory
通常以編程的方式被創(chuàng)建不同的是,ApplicationContext
能以聲明的方式創(chuàng)建,如使用ContextLoader
。當(dāng)然你也可以使用ApplicationContext
的實(shí)現(xiàn)之一來(lái)以編程的方式創(chuàng)建ApplicationContext
實(shí)例。首先,讓我們先分析ContextLoader
接口及其實(shí)現(xiàn)。
ContextLoader
機(jī)制有兩種方式,ContextLoaderListener
和ContextLoaderServlet
,他們功能相同但是listener不能在Servlet2.3容器下使用。Servlet2.4規(guī)范中servlet context listeners需要在web應(yīng)用啟動(dòng)并能處理初始請(qǐng)求時(shí)立即運(yùn)行。(servlet context listener關(guān)閉的時(shí)候也是相同的)。servlet context listener是初始化Spring ApplicationContext
理想的方式。你可能愿意選擇ContextLoaderListener
,雖然是一樣的,但決定權(quán)在于你。你可以查看ContextLoaderServlet
的Javadoc來(lái)獲得更詳細(xì)的信息。
可以象下面所示例的一樣使用ContextLoaderListener
注冊(cè)一個(gè)ApplicationContext
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- or use the ContextLoaderServlet
instead of the above listener
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->
監(jiān)聽(tīng)器首先檢查contextConfigLocation
參數(shù),如果它不存在,它將使用/WEB-INF/applicationContext.xml
作為默認(rèn)值。如果已存在,它將使用分隔符(逗號(hào)、冒號(hào)或空格)將字符串分解成應(yīng)用上下文件位置路徑??梢灾С謅nt-風(fēng)格的路徑模式,如/WEB-INF/*Context.xml
(WEB-INF文件夾下所有以"Context.xml"結(jié)尾的文件)。或者/WEB-INF/**/*Context.xml
(WEB-INF文件夾及子文件夾下的以"Context.xml"結(jié)尾的文件)。
ContextLoaderServlet
同ContextLoaderListener
一樣使用'contextConfigLocation'
參數(shù)。