Spring IoC容器之神通廣大的ApplicationContext

上一篇講了Spring IoC容器服務(wù)提供者BeanFactory。這一篇主要來研究下ApplicationContext。

在開始之前,我們需要知道Spring IoC容器和IoC Service Provider之間的關(guān)系:IoC Service Provider是Spring IoC容器體系的一部分。


Spring的IoC容器和IoC Service Provider之間的關(guān)系

在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)系如下:


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)識。

Resource和ResourceLoader類層次圖

對于以上圖,簡單來說就是三步:

1 首先對資源進行定義,于是就有了Resource接口。
2 然后,想辦法如何將資源加載進來,有了ResourceLoader接口以及各種實現(xiàn)。
3 如何加載多個資源,有了PathMatchingResourcePatternResolver實現(xiàn)

講了資源和加載策略,我們再來看看ApplicationContext是如何將它們給整合進來的。


ApplicationContext和資源加載策略的關(guān)系

繼承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>
MessageSource類層次結(jié)構(gòu)

國際化的使用場景如下:

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)聽者對事件的處理接口對事件進行處理。

事件三要素類結(jié)構(gòu)

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

BeanFactory和ApplicationContext繼承關(guān)系

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


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)容之后,對每一種特性的來龍去脈都能了如指掌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容