IoC容器15——ApplicationContext的附加功能

「第2節 註解的監聽器「中關於SpEL表達式做爲過濾條件的部分須要重看java

正如以前章節介紹的,org.springframework.beans.factory包提供了管理和操做bean的基礎功能,包括編程的方式。org.springframework.context包添加了ApplicationContext接口,它擴展了BeanFactory接口,同時也擴展了其它接口以更多的應用程序框架導向的風格來提供附加的功能(???)。許多人以徹底聲明的風格使用ApplicationContext,甚至不用編程的方式建立它;做爲替代,依賴於像COntextLoader這樣類的支持自動實例化ApplicationContext,做爲Java EE web應用程序正常啓動過程的一部分。web

爲了以更加面向框架的風格來加強BeanFactory功能,context包也提供了下面的功能:spring

  • 經過MessageSource接口,訪問國際化風格的消息;
  • 經過ResourceLoader接口,訪問資源,例如URL和文件;
  • 經過使用ApplicationEventPublisher接口,將事件發佈到實現ApplicationListener接口的bean;
  • 經過HierarchicalBeanFactory接口加載多個(結構層次化的)上下文,容許每一個關注一層,例如應用程序的web層。

1 使用MessageSource進行國際化

ApplicationContext接口擴展了MessageSource接口,所以提供了國際化的功能。Spring也提供了HierarchicalMessageSource接口,它能夠層次化的解析消息。這些接口一塊兒爲Spring消息解析提供基礎。定義在這些接口中的方法包括:編程

  • String getMessage(String code, Object[] args, String default, Locale loc):用於從MessageSource取得消息的基礎方法。當沒有找到用於特定locale的消息,將會使用默認的消息。任何傳入的參數會變爲替代值,使用標準庫提供的MessageFormat功能;windows

  • String getMessage(String code, Objectp[] args, Locale loc):本質上與上面的方法相同,不一樣點在於:沒有指定默認消息,若是不能找到消息會拋出NoSuchMessageException;設計模式

  • 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。任何解析消息的請求都會以JDK標準方式經過ResourceBundle來處理。爲了示例的目的,假設其中兩個資源束文件的內容是:服務器

# 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中,這個文件位於類路徑的根。messageSource bean定義經過它的basenames屬性引用了一系列的資源束。三個文件的名字以列表的形式被傳遞給basenames屬性,這三個文件存在於類路徑的根而且名爲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,若是想要使用英國的語言環境解析消息,須要分別建立名爲format_en_GB.properties、exceptions_en_GB.properties和windows_en_GB.properties的文件。

典型的,語言環境解析被應用程序運行的環境管理。在這個例子中,使用哪一個(British)語言環境手動指定。

# 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);
}

上面程序的運行輸出是:

Ebagum lad, the 'userDao' argument is required, I say, required.

也可使用MessageSourceAware接口得到任何已定義的MessageSource的引用。任何在ApplicationContext中定義的實現了MessageSourceAware接口的bean在建立時都會被注入應用上下的MessageSource。

做爲ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource類。這個類支持相同的束文件可是比基於JDK的標準ResourceBundleMessageSource實現更加靈活。尤爲是他容許從任何Spring資源的位置讀取文件(不只僅從classpath)而且支持束配置文件的熱重載(同時有效的緩存它們)。詳細信息可查看查看ReloadableResourceBundleMessageSource javadoc。

2 標準和自定義事件

ApplicationContext中的事件處理經過ApplicationEvent類和ApplicationListener接口提供。若是一個實現了ApplicationListener接口的bean被部署到上下文中,每次一個ApplicationEvent發佈到ApplicationContext時,這個bean會被通知。實際上,這是標準的觀察者設計模式。

從Spring 4.2開始,事件基礎設施被明顯的提高而且提供了基於註解的模型和發佈任意事件,即對象不須要繼承ApplicationEvent。當一個對象被髮布,Spring將它包裹到一個事件中。

Spring提供瞭如下標準事件:

事件 說明
ContextRefreshedEvent 當ApplicationContext被初始化或刷新時發佈,例如使用ConfigurableApplicationContext接口的refresh()方法。「初始化」在這裏的意思是所用bean被加載,後處理器bean被發現和激活,單例被預初始化,而且ApplicationContext對象可使用。只要上下文沒有被關閉,刷新可被屢次出發,只要所選的ApplicationContext支持這種「熱」刷新。例如,XmlWebApplicationContext支持熱刷新,可是GenericApplicationContext不支持。
ContextStartedEvent 當ApplicationContext開始的時候發佈,例如使用ConfigurableApplicationContext接口的start()方法。」開始「在這裏的意思是全部LifeCycle bean接受到一個明確的開始信號。典型的,這個信號被用於在明確的中止以後重啓bean,但也能夠用於開啓沒有被配置爲自動開啓的組件,例如在初始化中沒有被開啓的組件。
ContextStoppedEvent 當ApplicationContext被中止時發佈,例如使用ConfigurableApplicationContext接口的stop()方法。「中止」在這裏的意思是全部生命週期bean接受到一個明確的中止信號。一個已中止的上下文能夠經過調用start()方法重啓。
ContextClosedEvent 當ApplicationContext被關閉時發佈,例如使用ConfigurableApplicationContext接口的close()方法。「關閉」在這裏的意思是全部的單例bean被銷燬。已關閉的上下文到達了生命週期的最後,它不能被刷新和重啓。
RequestHandleEvent 一個基於web的事件,通知全部的bean一個HTTP請求已被處理。這個事件在請求完成時被髮布。這個事件僅僅適用於使用Spring的DispatcherServlet的web應用程序。

也能夠建立和發佈自定義事件。這個例子展現了一個繼承了Spring的ApplcationEvent基類的簡單類:

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...

}

爲了發佈一個自定義事件,調用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使用自定義的事件被範型參數化。這意味着onApplicationEvent()方法能夠保持類型安全,避免任何向下轉型的須要。能夠根據須要註冊多個事件監聽器,可是請注意,默認的事件監聽器同步的接收事件。這意味着publishEvent()方法會被阻塞直到所用監聽器都完成了對這個事件的處理。這種阻塞和單線程處理的一個好處是當一個監聽器接收到事件,若是一個事務上下文是可用的它就在發佈者的事務上下文中操做。若是須要另外的事件發佈策略,能夠參考Spring的ApplicationEventMulticaster接口的javadoc。

下面的例子展現了用於註冊和配置上面的類的XML bean定義:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>

把它們放在一塊兒,當emailServie bean的sendEmail()方法被調用,若是有任何郵箱應被列入黑名單,一個類型爲BlackListEvent的自定義事件被髮布。blackListNotifier bean被註冊爲一個ApplicationListener,所以接收到BlackListEvent,此時它能夠通知適當的部分。

Spring的事件機制被設計爲同一個應用上下文中的Spring bean之間的簡單通信。然而,對於更加複雜的企業級集成需求,分開維護的Spring Integration項目提供了對於基於Spring編程模型的構建輕量級、面向模式的事件驅動架構提供了完整的支持。

基於註解的事件監聽器

從Spring 4.2開始,一個事件監聽器能夠被bean的任何標註了EventListener註解的公有方法註冊。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表達式由專用的上下文評估。下面的表格列出了可用於上下文的項目,所以能夠將它們用於條件事件處理:

名字 位置 描述 例子
事件 根對象 實際的ApplicationEvent #root.event
參數數組 根對象 用於調用對象的參數(數組的形式) #root.args[0]
參數名 評估上下文 任何方法參數的名字。若是因爲一些緣由名字不可用(例如沒有debug信息),參數名在#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...
}

這個特性在異步監聽器中不支持。

這個新方法會在每次處理完BlackListEvent後發佈一個新的ListUpdateEvent。若是想發佈多個事件,返回事件的Collection便可。

異步監聽器

若是須要一個特定的監聽器來異步的處理事件,只須要重用標準的@Async支持:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

使用異步事件時請記得如下限制:

  1. 若是事件監聽器拋出異常,它不會傳遞給調用者,查看AsyncUncaughtExceptionHandler得到更多信息;
  2. 這種事件監聽器不能發送回覆。若是須要發送一個事件所謂處理的結果,請注入ApplicationEventPublisher來手動發送事件。

監聽器排序

若是須要一個監聽器比另一個先被調用,能夠在方法聲明上添加@Order註解:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

範型事件

可使用範型來進一步定義事件結構。可使用EntityCreatedEvent<T>,其中T是被建立的實體類型。能夠建立以下的監聽器定義用於僅接收Person的EntityCreatedEvent:

@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()));
    }
}

這種方法不只用於ApplicationEvent,它能夠用於任何做爲事件而被髮送的對象。

3 方便的訪問低級資源

爲了更好的使用和理解應用上下文,用戶應該屬性Spring的資源抽象。

一個應用上下問是一個ResourceLoader,能夠用於加載Resource。一個Resource實質上JDK類java.net.URL的功能更豐富的版本,實際上,Resource的實如今適當的地方包裹了java.net.URL的一個實例。一個Resource能夠從幾乎任何位置以透明的方式獲取低級資源,包括從類路徑、文件系統位置、使用標準URL描述的任何位置,或其它地方。若是一個資源定位字符串是一個簡單的路徑而沒有指定前綴,則他們從何處而來取決於實際的應用上下文類型。

能夠配置一個bean部署到應用上下文來實現指定的回調接口,ResourceLoaderAware,在初始化時應用上下文自身所謂ResourceLoader傳入時被自動回調。能夠暴露Resource類型的屬性,用於訪問靜態資源;它們能夠像其它屬性同樣被注入。能夠指定這些Resource屬性爲簡單的字符串路徑,依靠被上下文自動註冊的、類型爲PropertyEditor的特定JavaBean,當bean被部署時將這些文本字符串轉換爲實際的Resource對象。

提供給ApplicationContext構造函數的位置路徑其實是資源字符串,而且以簡單的形式根據具體的上下文實現進行恰當的處理。ClassPathXmlApplicationContext將一個簡單的定位路徑視爲類路徑位置。也可使用帶特定前綴的位置路徑強制指定從類路徑或URL加載定義,而不考慮實際上下文類型。

4 方便的web應用程序應用上下文實例

能夠聲明式的建立ApplicationContext,例如使用ContextLoader。固然也可使用一個ApplicationContext實例編程式的建立ApplicationContext實例。

可使用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>

監聽器檢查contextConfigLocation參數。若是參數不存在,監聽器使用/WEB-INF/applicationContext.xml做爲默認值。當參數存在,監聽器使用預約義的分隔符(逗號、分號和空格)分裂字符串而且使用這些值做爲應用上下文的搜索路徑。Ant風格的路徑模式也被支持。例如/WEB-INF/*Context.xml用於匹配在WEB-INF文件夾中以Context.xml結尾的文件,同時/WEB-INF/**/*Context.xml匹配在WEB-INF任何子文件夾中的這樣的文件。

5 將Spring應用上下文部署爲Java EE RAR文件

能夠將Spring應用上下文部署爲RAR文件,將上下文、全部須要的bean類、庫JAR文件封裝進一個Java EE RAR部署單元。這等價於引導一個託管在Java EE環境中的獨立的應用上下文,它能夠訪問Jave EE服務器設施。RAR部署是更天然的方案用於替代無頭的WAR文件,實際上,沒有任何HTTP入口點的WAR文件僅用於在Java EE環境中引導Spring的應用上下文。

RAR部署是不須要HTTP入口點而只須要消息端點和計劃任務的應用上下文的理想選擇。在這種上下文中的bean可使用應用服務器資源例如JTA事務管理器、JNDI綁定的JDBC數據源、JMS鏈接工廠實例和任何經過Spring標準事務管理器和JNDI、JMX支持工具向JMX服務器平臺註冊的組件。應用程序組件也能夠經過Spring的TaskExecutor抽象與應用程序服務器的JCA工做管理器進行交互。

查看SpringContextResourceAdapter類的javadoc來獲取RAR部署的配置細節。

對於將Spring應用上下文做爲Java EE RAR文件的簡單部署:將全部所用應用類打包成一個RAR文件,這是一個具備不一樣擴展名的標準JAR文件。將全部必需的庫JAR添加到RAR存檔的根目錄中。添加META-INF/ra.xml部署描述符(注入SpringContextResourceAdapter的javadoc中展現的那樣)和全部相關的Spring XML bean定義文件(通常是META-INF/applicationContext.xml),將獲得的RAR文件放入應用服務器的部署文件夾。

這樣的RAR部署單元通常是自持的;它們不對外暴露組件,甚至相同應用的其它組件。與基於RAR的ApplicationContext交互通常經過與其它模塊共享JMS目的地的方式。基於RAR的ApplicationContext也週期執行任務、對文件系統中的新文件進行響應(或相似物)。若是須要容許外部的同步訪問,能夠暴露RMI端點,它固然也能夠用於相同機器上的其它應用模塊。

相關文章
相關標籤/搜索