标題
正如本章介紹中所讨論的,該org.springframework.beans.factory 包提供了用于管理和操作bean的基本功能,包括以程式設計方式。該org.springframework.context軟體包添加了 ApplicationContext 擴充BeanFactory界面的界面,以及擴充其他界面以提供更多應用程式架構導向風格的附加功能。許多人使用ApplicationContext完全聲明的方式,甚至沒有以程式設計方式建立它,而是依賴支援類ContextLoader來自動執行個體化 ApplicationContextJava EE Web應用程式正常啟動過程的一部分。
為了增強BeanFactory面向架構的風格的功能,上下文包還提供以下功能:
- 通過MessageSource界面通路i18n風格的消息。
- 通過ResourceLoader界面通路資源,如URL和檔案。
- 事件釋出到即實作ApplicationListener接口的bean ,通過使用ApplicationEventPublisher接口。
加載多個(分層)上下文,通過HierarchicalBeanFactory接口允許每個上下文關注某個特定層,例如應用程式的Web層 。
一、使用MessageSource進行國際化
該ApplicationContext接口擴充了一個稱為的接口MessageSource,是以提供了國際化(i18n)功能。Spring還提供了HierarchicalMessageSource可以分層解析消息的接口。這些接口一起為Spring特效消息解析提供了基礎。這些接口上定義的方法包括:
- String getMessage(String code, Object[] args, String default, Locale loc):用于從中檢索消息的基本方法MessageSource。如果未找到指定語言環境的消息,則使用預設消息。使用MessageFormat标準庫提供的功能,傳入的任何參數都将成為替換值。
- String getMessage(String code, Object[] args, Locale loc):與前面的方法基本相同,但有一點不同:不能指定預設消息; 如果無法找到消息,NoSuchMessageException則會抛出a。
- String getMessage(MessageSourceResolvable resolvable, Locale locale):在前面的方法中使用的所有屬性也都包含在名為的類中 MessageSourceResolvable,您可以使用該方法。
當一個ApplicationContext被加載時,它會自動搜尋MessageSource 上下文中定義的一個bean。這個bean必須有名字messageSource。如果找到這樣的一個bean,所有對前面方法的調用都被委托給消息源。如果找不到消息源,則ApplicationContext嘗試查找包含具有相同名稱的bean的父項。如果是這樣,它使用該bean作為MessageSource。如果 ApplicationContext無法找到任何消息源,DelegatingMessageSource則會執行個體化一個空 以便能夠接受對上面定義的方法的調用。
Spring提供了兩個MessageSource實作,ResourceBundleMessageSource并且 StaticMessageSource。兩者都是HierarchicalMessageSource為了做嵌套消息傳遞而實作的。這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>
在這個例子中,假設你在你的類路徑中定義了三個資源包,分别叫做format,exceptions和windows。任何解析消息的請求都将以通過ResourceBundles解析消息的JDK标準方式進行處理。出于示例的目的,假設上述兩個資源封包件的内容是……
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
MessageSource下一個示例顯示了執行功能的程式。請記住,所有ApplicationContext實作都是MessageSource 實作,是以可以轉換為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);
}
從上述程式産生的輸出将是…
Alligators rock!
總之,這個MessageSource被定義在一個叫做的檔案中beans.xml,它存在于你的類路徑的根目錄下。該messageSourcebean定義是指通過它的一些資源包的basenames屬性。這是在清單中傳遞的三個檔案basenames屬性存在于你的classpath根目錄的檔案,被稱為format.properties,exceptions.properties和 windows.properties分别。
下一個示例顯示傳遞給消息查找的參數; 這些參數将轉換為字元串并插入查找消息中的占位符。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource 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);
}
}
調用該execute()方法的結果輸出将是…
The userDao argument is required.
關于國際化(i18n),Spring的各種MessageSource 實作遵循與标準JDK相同的區域設定分辨率和回退規則 ResourceBundle。總之,和繼續該示例messageSource先前定義的,如果你想解析British(消息en-GB)語言環境中,您将建立檔案名為format_en_GB.properties,exceptions_en_GB.properties和 windows_en_GB.properties分别。
通常,區域設定解析由應用程式的周圍環境管理。在這個例子中,(英國)消息将被解析的地區是手動指定的。
#在exceptions_en_GB.properties中
argument.required = Ebagum lad,我認為需要{0}參數。
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);
}
從上述程式運作得到的輸出将是…
Ebagum lad, the 'userDao' argument is required, I say, required.
您還可以使用該MessageSourceAware界面來擷取MessageSource已定義的任何參考 。任何在ApplicationContext實作MessageSourceAware接口的bean中定義 MessageSource的bean都會在建立和配置bean時注入應用程式上下文。
作為一種選擇ResourceBundleMessageSource,Spring提供了一個 ReloadableResourceBundleMessageSource類。該變體支援相同的封包件格式,但比标準的基于JDK的ResourceBundleMessageSource實作更靈活 。特别是,它允許從任何Spring資源位置(而不僅僅是從類路徑)讀取檔案,并支援熱重載bundle屬性檔案(同時有效地緩存它們)。
二、标準和自定義事件
ApplicationContext通過ApplicationEvent 類和ApplicationListener接口提供事件處理。如果實作ApplicationListener接口的beanA 部署到上下文中,則每次 ApplicationEvent釋出到該ApplicationContextbean時,都會通知該beanA。實質上,這是标準Observer設計模式。
Spring提供了以下标準事件:
Event | Explanation |
---|---|
ContextRefreshedEvent | 在ApplicationContext上下文中初始化或者重新整理。例如,使用ConfigurableApplicationContext中的refresh()方法。這裡的“初始化”意味着所有的bean都被加載,檢測并激活後處理器bean,單例被預先執行個體化,并且該ApplicationContext對象已準備好使用。隻要上下文尚未關閉,重新整理可以多次觸發,前提是所選内容ApplicationContext實際上支援“熱”重新整理。例如,XmlWebApplicationContext支援熱點重新整理,但GenericApplicationContext不支援 。 |
ContextStartedEvent | 在ApplicationContext啟動時釋出,使用ConfigurableApplicationContext上下文中的start()方法。這裡的“開始”意味着所有的Lifecycle bean都會收到明确的啟動信号。通常,此信号用于在顯式停止後重新啟動Bean,但它也可用于啟動尚未配置為自動啟動的元件,例如尚未啟動初始化的元件。 |
ContextStoppedEvent | 在ApplicationContext停止時釋出,使用ConfigurableApplicationContext上下文中的stop()方法。這裡“停止”意味着所有的Lifecycle bean都會收到明确的停止信号。停止的上下文可以通過start()呼叫重新啟動 。 |
ContextClosedEvent | 在ApplicationContext關閉時釋出,使用界面close()上的方法 ConfigurableApplicationContext。這裡的“關閉”意味着所有的單例bean被銷毀。封閉的環境達到其生命的盡頭; 它不能被重新整理或重新啟動。 |
RequestHandledEvent | 一個特定于web的事件,告知所有bean HTTP請求已被服務。此事件在請求完成後釋出。此事件僅适用于使用Spring的Web應用程式DispatcherServlet。 |
您還可以建立和釋出自己的自定義事件。這個例子示範了一個擴充Spring ApplicationEvent基類的簡單類:
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}
-
釋出
要釋出自定義ApplicationEvent,請調用ApplicationEventPublisher中的publishEvent()方法 。通常這是通過建立一個實作ApplicationEventPublisherAware并注冊為Spring bean 的類來完成的 。以下示例示範了這樣一個類:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}
在配置時,Spring容器将檢測到該EmailService實作 ApplicationEventPublisherAware并将自動調用 setApplicationEventPublisher()。實際上,傳入的參數将是Spring容器本身; 你隻是通過它的ApplicationEventPublisher接口與應用程式上下文進行 互動。
-
接收
要接收該定制ApplicationEvent,請建立一個實作 ApplicationListener并将其注冊為Spring bean的類。以下示例示範了這樣一個類:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
請注意,ApplicationListener它通常用您的自定義事件的類型進行參數化BlackListEvent。這意味着該onApplicationEvent()方法可以保持類型安全,避免任何向下轉換的需要。您可以根據需要注冊許多事件偵聽器,但請注意,預設情況下事件偵聽器會同步接收事件。這意味着publishEvent()方法會阻塞,直到所有聽衆完成處理事件。這種同步和單線程方法的一個優點是,當偵聽器接收到事件時,如果事務上下文可用,它将在釋出者的事務上下文内部運作。如果需要另一個事件釋出政策,請參考Spring ApplicationEventMulticaster界面的javadoc 。
以下示例顯示了用于注冊和配置上述每個類的bean定義:
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="[email protected]"/>
</bean>
綜合起來,當調用bean 的sendEmail()方法時emailService,如果有任何應該被列入黑名單的電子郵件,BlackListEvent則會釋出類型的自定義事件 。這個blackListNotifierbean被注冊為一個 ApplicationListener并且是以接收到BlackListEvent,在此時它可以通知适當的各方。
Spring的事件機制被設計為在同一個應用程式上下文中的Spring bean之間進行簡單的通信。然而,對于更複雜的企業內建需求,單獨維護的 Spring Integration項目為建構輕量級,面向模式的事件驅動架構提供完全支援, 該架構基于着名的Spring程式設計模型。
三、基于注釋的事件監聽器
從Spring 4.2開始,可以通過
@EventListener
注釋在托管bean的任何公共方法上注冊事件偵聽器。該BlackListNotifier可改寫如下:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
正如您在上面看到的,方法簽名再次聲明它監聽的事件類型,但是這次使用靈活的名稱并且不實作特定的監聽器接口。隻要實際事件類型在其實作層次結構中解析泛型參數,事件類型也可以通過泛型進行縮小。
如果你的方法應該監聽幾個事件,或者如果你想要根本沒有參數定義它,事件類型也可以在注釋本身上指定:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
也可以通過condition注釋的屬性添加額外的運作時過濾,該過濾器定義了一個SpEL表達式,該表達式應比對以實際調用特定事件的方法。
例如,如果test事件的屬性等于foo:我們的通知器可以被重寫為僅被調用:
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每個SpEL表達式再次評估一個專用的上下文。下表列出了可用于上下文的項目,以便可以将它們用于條件事件處理:
Table 8. Event SpEL available metadata
Name | Location | Description | Example |
---|---|---|---|
root object | The actual ApplicationEvent | root.event | |
用于調用目标的參數(如數組) | |||
Argument name | 任何方法參數的名稱。如果由于某種原因名稱是不可用(例如,沒有調試資訊),參數名稱也是在現有的#a<#arg> 地方#arg代表的說法指數(從0開始)。 | blEvent或者#a0(也可以使用#p0或#p<#arg>标記作為别名) |
注意#root.event,即使您的方法簽名實際引用了已釋出的任意對象,也可以通路基礎事件。
如果您需要釋出一個事件作為處理另一個事件的結果,隻需更改方法簽名以傳回應該釋出的事件,如下所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
異步監聽器不支援這個東西
這種新方法将為上述方法的ListUpdateEvent每個BlackListEvent處理釋出一個新的方法。如果您需要釋出多個事件,則隻需傳回一些Collection事件。
四、異步監聽器
如果您希望特定的偵聽器異步處理事件,simply reuse the regular
@Async
support:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用異步事件時請注意以下限制:
如果事件監聽器抛出Exception它不會傳播給調用者,請檢查AsyncUncaughtExceptionHandler更多細節。
這種事件監聽器不能發送回複。如果您需要發送另一個事件作為處理結果,請注入ApplicationEventPublisher以手動發送事件。
五、(Ordering listeners)監聽器的順序
如果您需要在另一個之前調用偵聽器,隻需将該
@Order
注釋添加到方法聲明中:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
六、通用事件
您也可以使用泛型來進一步定義事件的結構。考慮 建立實際實體的類型
EntityCreatedEvent<T>
在哪裡T。您可以建立以下偵聽器定義以僅接收
EntityCreatedEvent
以下内容 Person:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于類型擦除,隻有當被觸發的事件解析了事件偵聽器過濾的泛型參數時(這是類似的
class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })
,這才會起作用 。
在某些情況下,如果所有事件都遵循相同的結構(這應該是上述事件的情況),則這可能變得非常乏味。在這種情況下,您可以實作
ResolvableTypeProvider
以引導架構超出運作時環境所提供的範圍:
public class EntityCreatedEvent<T>
extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
This works not only for ApplicationEvent but any arbitrary object that you’d send as an event.
七、友善地通路低水準資源
為了最佳使用和了解應用程式上下文,使用者通常應該熟悉Spring的Resource抽象,如“ 資源 ”一章所述 。
應用程式上下文是一個ResourceLoader可以用來加載Resources 的應用程式上下文。A Resource本質上是JDK類的功能更豐富的版本java.net.URL,實際上,在适當Resource的java.net.URL地方包裝一個執行個體。A Resource可以以透明的方式從幾乎任何位置擷取底層資源,包括類路徑,檔案系統位置,任何可用标準URL描述的地方以及其他一些變體。如果資源位置字元串是一個沒有任何特殊字首的簡單路徑,那麼這些資源來自特定且适合于實際應用程式上下文類型。
您可以配置一個部署到應用程式上下文中的bean來實作特殊的回調接口,ResourceLoaderAware在初始化時自動調用回應用程式上下文本身作為 ResourceLoader。您還可以公開Resource用于通路靜态資源的類型屬性; 它們将像其他任何屬性一樣被注入到它中。您可以将這些Resource屬性指定為簡單的String路徑,并依賴PropertyEditor由上下文自動注冊的特殊JavaBean ,以便Resource在部署Bean時将這些文本字元串轉換為實際對象。
提供給ApplicationContext構造函數的位置路徑實際上是資源字元串,并且以簡單形式适當地處理特定的上下文實作。ClassPathXmlApplicationContext将簡單的位置路徑視為類路徑位置。您還可以使用帶有特殊字首的位置路徑(資源字元串)來強制從類路徑或URL中加載定義,而不管實際的上下文類型如何。
八、友善的Web應用程式的ApplicationContext執行個體化
您可以ApplicationContext通過使用例如a來聲明性地建立執行個體 ContextLoader。當然,您也可以ApplicationContext使用其中一種ApplicationContext實作方式程式設計建立執行個體。
您可以ApplicationContext使用ContextLoaderListener如下注冊一個:
<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>
監聽者檢查contextConfigLocation參數。如果該參數不存在,則偵聽器将/WEB-INF/applicationContext.xml用作預設值。當參數确實存在時,偵聽器使用預定義的分隔符(逗号,分号和空白)來分隔字元串,并将這些值用作應用程式上下文将被搜尋的位置。也支援Ant風格的路徑模式。例子是/WEB-INF/Context.xml名稱以“Context.xml”結尾的所有檔案,駐留在“WEB-INF”目錄中,并且/WEB-INF/*/*Context.xml對于“WEB-INF”的任何子目錄中的所有這些檔案。
九、将Spring ApplicationContext部署為Java EE RAR檔案
可以将Spring ApplicationContext部署為RAR檔案,将上下文及其所有必需的bean類和庫JAR封裝到Java EE RAR部署單元中。這相當于引導了一個獨立的ApplicationContext,它隻是在Java EE環境中托管,能夠通路Java EE伺服器設施。RAR部署是部署無頭WAR檔案的場景中更自然的選擇,實際上,WAR檔案沒有任何HTTP入口點,僅用于在Java EE環境中引導Spring ApplicationContext。
RAR部署非常适合不需要HTTP入口點但僅包含消息端點和預定作業的應用程式上下文。在這種情況下,Bean可以使用應用伺服器資源,例如JTA事務管理器和JNDI綁定的JDBC DataSources和JMS ConnectionFactory執行個體,也可以通過Spring的标準事務管理和JNDI和JMX支援工具向平台的JMX伺服器注冊。應用程式元件還可以通過Spring的TaskExecutor抽象與應用程式伺服器的JCA WorkManager進行互動。
要将Spring ApplicationContext簡單部署為Java EE RAR檔案:将所有應用程式類打包到RAR檔案中,該檔案是具有不同檔案擴充名的标準JAR檔案。将所有必需的庫JAR添加到RAR歸檔的根目錄中。添加一個“META-INF / ra.xml”部署描述符(如SpringContextResourceAdapters javadoc中所示)和相應的Spring XML bean定義檔案(通常為“META-INF / applicationContext.xml”),并放棄生成的RAR檔案到您的應用程式伺服器的部署目錄。
這種RAR部署單元通常是獨立的; 它們不會将元件暴露給外部世界,甚至不會暴露給同一應用程式的其他子產品。與基于RAR的ApplicationContext的互動通常通過它與其他子產品共享的JMS目标發生。例如,基于RAR的ApplicationContext也可以排程一些作業,對檔案系統中的新檔案(或諸如此類)作出反應。如果需要允許從外部進行同步通路,則可以導出RMI端點,這當然可以由同一台機器上的其他應用程式子產品使用。
好啦,ApplicationContext的附加功能,就是這麼多東西,平時多看看,在用的時候,就能得心應手。