spring中的事件機制(譯)

標準定制事件

在ApplicationContext中,事件的處理是通過ApplicationEvent和ApplicationListener 兩個類來完成的。在容器中實現了ApplicationListener的接口的類,在每次ApplicationEvent事件發布的時候,都會被觸發通知。這基本就是標準的觀察者模式了。

在spring4.2,事件處理機制顯著提升,提供了基于注解的方式發布事件,對象沒必要再繼承ApplicationEvent這個類。當這個注解對象被published的時候,框架會為你自動包裝成一個事件。

spring提供如下標準事件:

Event Explanation
ContextRefreshedEvent Published when the ApplicationContext is initialized or refreshed, for example, using the refresh() method on the ConfigurableApplicationContext interface. "Initialized" here means that all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use. As long as the context has not been closed, a refresh can be triggered multiple times, provided that the chosen ApplicationContext actually supports such "hot" refreshes. For example, XmlWebApplicationContext supports hot refreshes, but GenericApplicationContext does not.
ContextStartedEvent Published when the ApplicationContext is started, using the start() method on the ConfigurableApplicationContext interface. "Started" here means that all Lifecycle beans receive an explicit start signal. Typically this signal is used to restart beans after an explicit stop, but it may also be used to start components that have not been configured for autostart , for example, components that have not already started on initialization.
ContextStoppedEvent Published when the ApplicationContext is stopped, using the stop() method on the ConfigurableApplicationContext interface. "Stopped" here means that all Lifecycle beans receive an explicit stop signal. A stopped context may be restarted through a start() call.
ContextClosedEvent Published when the ApplicationContext is closed, using the close() method on the ConfigurableApplicationContext interface. "Closed" here means that all singleton beans are destroyed. A closed context reaches its end of life; it cannot be refreshed or restarted.
RequestHandledEvent A web-specific event telling all beans that an HTTP request has been serviced. This event is published after the request is complete. This event is only applicable to web applications using Spring’s DispatcherServlet.

你也可以創建或者發布自己的事件。如下的例子演示了一個簡單的類繼承了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...

}

我們通常使用ApplicationEventPublisher類的publishEvent()方法來發布一個ApplicationEvent事件。

典型的做法是創建一個類,實現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...
        }

}

在配置啟動階段,容器會去檢測實現了ApplicationEventPublisherAware 接口的EmailService類,同時會自動調用setApplicationEventPublisher()方法。

實際上,注入進去的參數就是容器自己。我們可以通過ApplicationEventPublisher接口,輕松的和容器的上下文交互。

創建一個類,實現ApplicationListener接口,并注冊到容器中,就可以接收自定義的ApplicationEvent事件。如下例子:

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()這個方法會阻塞,直到所有的監聽器完成事件的處理。這種同步單線程的方式,通常是單個監聽器接收單個事件的方式,他和發布事件是在同一個事務中處理的。

另外一種發布策略可以參照下javadoc的ApplicationEventMulticaster這個接口。

如下是上面兩個類的一些配置,當然,可以通過spring的注解來進行類的實例化,至于參數,可以結合spring-boot,進行配置文件注入。

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

結合上述,當sendEmail()方法被調用的時候,如果有任何的郵件是屬于黑名單的話,BlackListEvent事件就會被發布。blackListNotifier這個注冊成了ApplicationListener,所以,能收到BlackListEvent事件。

spring 的事件機制是被設計用在同一個應用的上下文中不同的bean進行交互用的。當然,更復雜的企業級集成需求,Spring Integration 這個項目在著名的spring編程模型之上提供了比較完備的支持:輕便的,pattern-oriented,基于事件驅動的架構。

基于注解的事件監聽

spring4.2,事件監聽可以通過在一個公共的方法上用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
expression
表達式的事件類型,將會被invoke到特殊的事件去處理。

例如,上面的notifier可以被重寫成“只有事件中的test屬性等于foo的時候,才觸發process方法”:

@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
        // notify appropriate parties via notificationAddress...
}

每個spel表達式針對一個專用的上下文。下面的表格列出來一些對上下文比較有用的項,可以選擇使用:

name location description example
Event root object The actual ApplicationEvent #root.event
Arguments array root object The arguments (as array) used for invoking the target #root.args[0]
Argument name evaluation context Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). #blEvent or #a0 (one can also use #p0 or #p<#arg> notation as an alias).

Table 8. Event SpEL available metadata

name location description example
Event root object The actual ApplicationEvent #root.event
Arguments array root object The arguments (as array) used for invoking the target #root.args[0]
Argument name evaluation context Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). #blEvent or #a0 (one can also use #p0 or #p<#arg> notation as an alias).

root.event這個選項表明了根本的事件類型,盡管你的方法簽名中指向的是真實的另外一個隨意的發布事件。

如果你需要對另外一個事件的處理結果來發布一個事件,你只需要修改方法的返回簽名,把他改成你需要發布的事件類型即可,如下:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress and
        // then publish a ListUpdateEvent...
}

This feature is not supported for asynchronous listeners.

對于每個BlackListEvent事件的處理,這個方法將會發布一個新的事件:ListUpdateEvent。如果你需要發布多個事件,只要把返回結果改成事件的集合Collection即可。

異步監聽

如果想創建一個特殊的監聽器來異步的處理事件,需要regular @Async
support
:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
        // BlackListEvent is processed in a separate thread
}
  • 需要清楚的知道用異步事件的限制如下:

    1、如果事件監聽拋出一個Exception,這個異常不會傳播到調用者,參見AsyncUncaughtExceptionHandler獲取更多信息。

    2、這種異步的監聽是沒辦法返回結果的。如果你需要對處理結果再發一個事件,注入ApplicationEventPublisher來手工發布事件。

順序監聽

如果你想定義監聽器的順序,加上@Order注解即可:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,948評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,958評論 6 342
  • 什么是Spring Spring是一個開源的Java EE開發框架。Spring框架的核心功能可以應用在任何Jav...
    jemmm閱讀 16,555評論 1 133
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風情閱讀 1,726評論 0 3
  • 山遠路迢迢, 心夢一尺遙。 揮書吐唐韻, 點墨見情豪。 蓬萊團紫氣, 海棠現妖嬈。 常感花事近, 冰釋冬漸消。 天...
    簡書作者木瓜閱讀 249評論 12 8