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