上一篇講了Spring IoC容器服務(wù)提供者BeanFactory。這一篇主要來研究下ApplicationContext。
在開始之前,我們需要知道Spring IoC容器和IoC Service Provider之間的關(guān)系:IoC Service Provider是Spring IoC容器體系的一部分。

在spring家族中,承諾提供這項IoC Service Provider服務(wù)的主要有以下這兩個接口:
- BeanFactory
基礎(chǔ)類型IoC容器,提供完整的IoC服務(wù)支持。默認(rèn)采用延遲加載,也就是在需要的時候,才去召喚、創(chuàng)建對象。故在項目啟動的時候可以非???。缺點是在使用時,第一次召喚對象會比較慢。- ApplicationContext
ApplicationContext在BeanFactory的基礎(chǔ)上構(gòu)建,是相對比較高
級的容器實現(xiàn),除了擁有BeanFactory的所有支持,ApplicationContext還提供了其他高級特性。ApplicationContext所管理的對象,在該類型容器啟動之后,默認(rèn)全部初始化并綁定完成。啟動時需要大量資源,但在真正使用的時候,可以迅速將對象召喚出來。
BeanFactory和ApplicationContext繼承關(guān)系如下:

從上面的繼承圖,可以看出ApplicationContext支持的功能:
對象的創(chuàng)建管理(BeanFactory),上篇文章已講,本篇不重復(fù)。
統(tǒng)一資源加載策略(ResourceLoader)
國際化信息支持(MessageSource)
容器內(nèi)部事件發(fā)布(ApplicationEventPublisher)
多配置模塊加載的簡化
ApplicationContext只是一個接口,聲明了一些功能,但并沒有指定具體的實現(xiàn)。ApplicationContext常用的實現(xiàn)類有以下幾個:
- FileSystemXmlApplicationContext
從文件系統(tǒng)加載bean定義以及相關(guān)資源的ApplicationContext實現(xiàn)- ClassPathXmlApplicationContext
從Classpath加載bean定義以及相關(guān)資源的ApplicationContext實現(xiàn)- XmlWebApplicationContext
Spring提供的用于Web應(yīng)用程序的ApplicationContext實現(xiàn)
0x01 統(tǒng)一資源加載策略
這里有兩個關(guān)鍵名詞:資源和加載策略。
資源用Resource接口去抽象,加載策略用ResourceLoader接口去抽象。
資源Resource
資源Resource最終體現(xiàn)為一個文件對象File,以及一些關(guān)于文件的描述,如是否關(guān)閉,是否存在,文件名,路徑等。Resource接口定義如下:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
根據(jù)不同的場景場合,資源的來源也不一樣,故對資源接口Resource有不同的實現(xiàn)類。
FileSystemResource:以文件或者URL的形式對該類型資源進行訪問.
ClassPathResource:從ClassPath中加載具體資源并進行封裝.
UrlResource:通過java.net.URL進行的具體資源查找定位的實現(xiàn)類.
ByteArrayResource。將字節(jié)(byte)數(shù)組提供的數(shù)據(jù)作為一種資源進行封裝
加載策略ResourceLoader
資源是有了,但如何去查找和定位這些資源,則應(yīng)該是ResourceLoader的職責(zé)所在了。org.springframework.core.io.ResourceLoader接口是資源查找定位策略的統(tǒng)一抽象,具體的資源查找定位策略則由相應(yīng)的ResourceLoader實現(xiàn)類給出。
資源加載策略的接口定義如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
對不同的資源,也應(yīng)該有不同的加載策略。已經(jīng)實現(xiàn)的加載策略如下:
DefaultResourceLoader:支持路徑以classpath:和URL前綴打頭的資源加載。
FileSystemResourceLoader:從文件系統(tǒng)中加載具體資源并進行封裝.
ResourcePatternResolver:批量查找的ResourceLoader
常用的是ResourcePatternResolver資源加載器。因為可以掃描某個指定包下面的所有資源。接口定義如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
ResourcePatternResolver在繼承ResourceLoader原有定義的基礎(chǔ)上,又引入了Resource[] getResources(String)方法定義,以支持根據(jù)路徑匹配模式返回多個Resources的功能。ResourcePatternResolver最常用的一個實現(xiàn)是org.springframework.core.io.support.PathMatchingResourcePatternResolver。
現(xiàn)在我們應(yīng)該對Spring的統(tǒng)一資源加載策略有了一個整體上的認(rèn)識。

對于以上圖,簡單來說就是三步:
1 首先對資源進行定義,于是就有了Resource接口。
2 然后,想辦法如何將資源加載進來,有了ResourceLoader接口以及各種實現(xiàn)。
3 如何加載多個資源,有了PathMatchingResourcePatternResolver實現(xiàn)
講了資源和加載策略,我們再來看看ApplicationContext是如何將它們給整合進來的。

繼承DefaultResourceLoader去加載資源,組合PathMatchingResourcePatternResolver去實現(xiàn)多個資源的獲取。這就是ApplicationContext的統(tǒng)一資源加載策略。
0x02 國際化支持MessageSource
對于Java中的國際化信息處理,主要涉及兩個類,即java.util.Locale和java.util.ResourceBundle。
1. Locale
不同的Locale代表不同的國家和地區(qū)每個國家和地區(qū)在Locale這里都有相應(yīng)的簡寫代碼表示, 包括語言代碼以及國家代碼,這些代碼是ISO標(biāo)準(zhǔn)代碼。如,Locale.CHINA代表中國,它的代碼表示
為zh_CN;Locale.US代表美國地區(qū),代碼表示為en_US;
Locale(String language)
Locale(String language, String country)
L ocale(String language, String country, String variant)
2. ResourceBundle
ResourceBundle用來保存特定于某個Locale的信息。ResourceBundle管理一組信息序列,所有的信息序列有統(tǒng)一的一個basename,如下:
messages.properties
messages_zh.properties
messages_zh_CN.properties
messages_en.properties
messages_en_US.properties
其中,文件名中的messages部分稱作ResourceBundle將加載的資源的basename,其他語言或地區(qū)的資源在basename的基礎(chǔ)上追加Locale特定代碼。
有了ResourceBundle對應(yīng)的資源文件之后,我們就可以通過ResourceBundle的
getBundle(String baseName, Locale locale)
方法取得不同Locale對應(yīng)的ResourceBundle,然后根據(jù)資源
的鍵取得相應(yīng)Locale的資源條目內(nèi)容。
Spring在Java SE的國際化支持的基礎(chǔ)上,進一步抽象了國際化信息的訪問接口,也就是
org.springframework.context.MessageSource,該接口定義如下:
public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws ?
NoSuchMessage Exception;
}
以上的對國際化的訪問也只是接口,要有具體的實現(xiàn)才能滿足我們的需求。
Spring提供了三種MessageSource的實現(xiàn),即StaticMessageSource、ResourceBundleMessage-
Source和ReloadableResourceBundleMessageSource。
最常用的就是ResourceBundleMessageSource
使用xml的方式注入如下:
<bean id="messageSource" class="org.springframework.context.support. ? ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages</value> <value>errorcodes</value>
</list>
</property>
</bean>

國際化的使用場景如下:
1 啟動的時候,加載默認(rèn)的國際化文件。
2 web訪問的時候,傳入Locale對象。
常用的,當(dāng)然是第二種場景。
代碼如下:
//獲取當(dāng)前請求線程的RequestContext
private static RequestContext getRequestContext() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
return new RequestContext(request);
}
//獲取本次請求的Locale
public static Locale getLocale() {
Locale locale = curLocale.get();
// 取得界面的Locale
return locale == null ? getRequestContext().getLocale() : locale;
}
0x03 事件的發(fā)布
事件發(fā)布三要素:
1 要有事件 EventObject
2 要有監(jiān)聽者 EventListener
3 要有發(fā)布者 EventPublisher
一句話整合這三要素:發(fā)布者遍歷內(nèi)部的監(jiān)聽者列表,然后調(diào)用各個監(jiān)聽者對事件的處理接口對事件進行處理。

再回顧一個ApplicationContext的類繼承樹,這次我們要講解的是ApplicationEventPublisher類。

下面是Spring容器內(nèi)部事件發(fā)布的實現(xiàn)類圖:

不難看出,ApplicationContext容器現(xiàn)在擔(dān)當(dāng)?shù)木褪鞘录l(fā)布者的角色。但ApplicationContext畢竟都只是接口,總要有具體的類去干活才行,實際的工作是另有其人去完成,這個事件發(fā)布的工作由AbstractApplicationContext委托給ApplicationEventMulticaster接口體系去完成(這里使用了策略模式)。但整體的體系結(jié)構(gòu)就是上面說的事件發(fā)布三要素。
Spring容器在啟動的時候,會在各個關(guān)鍵節(jié)點發(fā)布消息。這些事件有如下幾種:
ContextClosedEvent:ApplicationContext容器在即將關(guān)閉的時候發(fā)布的事件類型。
ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的時候發(fā)布的事件類型。
RequestHandledEvent:Web請求處理后發(fā)布的事件,其有一子類ServletRequestHandledEvent提供特定于Java EE的Servlet相關(guān)事件。
ApplicationReadyEvent:容器準(zhǔn)備好后,發(fā)布的事件
如果你對這些消息感興趣,那么你就將這個事件的監(jiān)聽器注冊進來。
實現(xiàn)listener接口,直接使用@Component注解即可。
例如,在容器啟動準(zhǔn)備完成后,打印啟動成功:
@Component
public class ServerApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
private Logger logger = LoggerFactory.getLogger(ServerApplicationListener.class);
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
logger.info("START APPLICATION SUCCESS ");
}
}
以上是監(jiān)聽Spring容器自己發(fā)布的事件。如果你想要使用Spring容器的事件處理機制,在自己認(rèn)為合適的地點,發(fā)布自定義的事件,然后來監(jiān)聽改事件,應(yīng)該怎么編寫代碼?
只需以下四個步驟:
1 繼承EventObject定義自己的事件
2 實現(xiàn)ApplicationListener,執(zhí)行事件處理邏輯
3 實現(xiàn)ApplicationEventPublisherAware接口,獲取ApplicationEventPublisher事件發(fā)布類,發(fā)布事件。
4 將自定義的事件處理器和事件發(fā)布類交給Spring容器托管
總結(jié):
ApplicationContext是Spring在BeanFactory基礎(chǔ)容器之上,提供的另一個IoC容器實現(xiàn)。它擁有許多BeanFactory所沒有的特性,包括統(tǒng)一的資源加載策略、國際化信息支持、容器內(nèi)事件發(fā)布以及簡化的多配置文件加載功能。本章對ApplicationContext的這些新增特性進行了詳盡的闡述。希望讀者在學(xué)習(xí)完本章內(nèi)容之后,對每一種特性的來龍去脈都能了如指掌。