事件驅動模型簡介
事件驅動模型也就是我們常說的觀察者,或者發布-訂閱模型;理解它的幾個關鍵點:
- 首先是一種對象間的一對多的關系;最簡單的如交通信號燈,信號燈是目標(一方),行人注視著信號燈(多方);
- 當目標發送改變(發布),觀察者(訂閱者)就可以接收到改變;
- 觀察者如何處理(如行人如何走,是快走/慢走/不走,目標不會管的),目標無需干涉;所以就松散耦合了它們之間的關系。
接下來先看一個用戶注冊的例子:

用戶注冊成功后,需要做這么多事:
1、加積分
2、發確認郵件
3、如果是游戲帳戶,可能贈送游戲大禮包
4、索引用戶數據
…………
問題:
- UserService和其他Service耦合嚴重,增刪功能比較麻煩;
- 有些功能可能需要調用第三方系統,如增加積分/索引用戶,速度可能比較慢,此時需要異步支持;這個如果使用Spring,可以輕松解決,后邊再介紹;
從如上例子可以看出,應該使用一個觀察者來解耦這些Service之間的依賴關系,如圖:

增加了一個Listener來解耦UserService和其他服務,即注冊成功后,只需要通知相關的監聽器,不需要關系它們如何處理。增刪功能非常容易。
這就是一個典型的事件處理模型/觀察者,解耦目標對象和它的依賴對象,目標只需要通知它的依賴對象,具體怎么處理,依賴對象自己決定。比如是異步還是同步,延遲還是非延遲等。
上邊其實也使用了DIP(依賴倒置原則),依賴于抽象,而不是具體。
還是就是使用了IoC思想,即以前主動去創建它依賴的Service,現在只是被動等待別人注冊進來。
其他的例子還有如GUI中的按鈕和動作的關系,按鈕和動作本身都是一種抽象,每個不同的按鈕的動作可能不一樣;如“文件-->新建”打開新建窗口;點擊“關閉”按鈕關閉窗口等等。
主要目的是:松散耦合對象間的一對多的依賴關系,如按鈕和動作的關系;
如何實現呢?面向接口編程(即面向抽象編程),而非面向實現。即按鈕和動作可以定義為接口,這樣它倆的依賴是最小的(如在Java中,沒有比接口更抽象的了)。
有朋友會問,我剛開始學的時候也是這樣:抽象類不也行嗎?記住一個原則:接口目的是抽象,抽象類目的是復用;所以如果接觸過servlet/struts2/spring等框架,大家都應該知道:
- Servlet<-----GenericServlet<-----HttpServlet<------我們自己的
- Action<------ActionSupport<------我們自己的
- DaoInterface<------××DaoSupport<-----我們自己的
從上邊大家應該能體會出接口、抽象類的主要目的了。現在想想其實很簡單。
在Java中接口還一個非常重要的好處:接口是可以多實現的,類/抽象類只能單繼承,所以使用接口可以非常容易擴展新功能(還可以實現所謂的mixin),類/抽象類辦不到。
Java GUI事件驅動模型/觀察者
扯遠了,再來看看Java GUI世界里的事件驅動模型吧:
如果寫過AWT/Swing程序,應該知道其所有組件都繼承自java.awt.Component抽象類,其內部提供了addXXXListener(XXXListener l) 注冊監聽器的方法,即Component與實際動作之間依賴于XXXListener抽象。
比如獲取焦點事件,很多組件都可以有這個事件,是我們知道組件獲取到焦點后需要一個處理,雖然每個組件如何處理是特定的(具體的),但我們可以抽象一個FocusListener,讓所有具體實現它然后提供具體動作,這樣組件只需依賴于FocusListener抽象,而不是具體。
還有如java.awt.Button,提供了一個addActionListener(ActionListener l),用于注冊點擊后觸發的ActionListener實現。
組件是一個抽象類,其好處主要是復用,比如復用這些監聽器的觸發及管理等。
JavaBean規范的事件驅動模型/觀察者
JavaBean規范提供了JavaBean的PropertyEditorSupport及PropertyChangeListener支持。
PropertyEditorSupport就是目標,而PropertyChangeListener就是監聽器,大家可以google搜索下,具體網上有很多例子。
Java提供的事件驅動模型/觀察者抽象
JDK內部直接提供了觀察者模式的抽象:
目標:java.util.Observable,提供了目標需要的關鍵抽象:addObserver/deleteObserver/notifyObservers()等,具體請參考javadoc。
觀察者:java.util.Observer,提供了觀察者需要的主要抽象:update(Observable o, Object arg),此處還提供了一種推模型(目標主動把數據通過arg推到觀察者)/拉模型(目標需要根據o自己去拉數據,arg為null)。
因為網上介紹的非常多了,請google搜索了解如何使用這個抽象及推/拉模型的優缺點。
接下來是我們的重點:spring提供的事件驅動模型。
Spring提供的事件驅動模型/觀察者抽象
首先看一下Spring提供的事件驅動模型體系圖:

事件
具體代表者是:ApplicationEvent:
1、其繼承自JDK的EventObject,JDK要求所有事件將繼承它,并通過source得到事件源,比如我們的AWT事件體系也是繼承自它;
2、系統默認提供了如下ApplicationEvent事件實現:

只有一個ApplicationContextEvent,表示ApplicationContext容器事件,且其又有如下實現:
- ContextStartedEvent:ApplicationContext啟動后觸發的事件;(目前版本沒有任何作用)
- ContextStoppedEvent:ApplicationContext停止后觸發的事件;(目前版本沒有任何作用)
- ContextRefreshedEvent:ApplicationContext初始化或刷新完成后觸發的事件;(容器初始化完成后調用)
- ContextClosedEvent:ApplicationContext關閉后觸發的事件;(如web容器關閉時自動會觸發spring容器的關閉,如果是普通java應用,需要調用ctx.registerShutdownHook();注冊虛擬機關閉時的鉤子才行)
注:org.springframework.context.support.AbstractApplicationContext抽象類實現了LifeCycle的start和stop回調并發布ContextStartedEvent和ContextStoppedEvent事件;但是無任何實現調用它,所以目前無任何作用。
目標(發布事件者)
具體代表者是:ApplicationEventPublisher及ApplicationEventMulticaster,系統默認提供了如下實現:

1、ApplicationContext接口繼承了ApplicationEventPublisher,并在AbstractApplicationContext實現了具體代碼,實際執行是委托給ApplicationEventMulticaster(可以認為是多播):
Java代碼

- public void publishEvent(ApplicationEvent event) {
- //省略部分代碼
- }
- getApplicationEventMulticaster().multicastEvent(event);
- if (this.parent != null) {
- this.parent.publishEvent(event);
- }
- }
我們常用的ApplicationContext都繼承自AbstractApplicationContext,如ClassPathXmlApplicationContext、XmlWebApplicationContext等。所以自動擁有這個功能。
2、ApplicationContext自動到本地容器里找一個名字為”“的ApplicationEventMulticaster實現,如果沒有自己new一個SimpleApplicationEventMulticaster。其中SimpleApplicationEventMulticaster發布事件的代碼如下:
Java代碼

- public void multicastEvent(final ApplicationEvent event) {
- for (final ApplicationListener listener : getApplicationListeners(event)) {
- Executor executor = getTaskExecutor();
- if (executor != null) {
- executor.execute(new Runnable() {
- public void run() {
- listener.onApplicationEvent(event);
- }
- });
- }
- else {
- listener.onApplicationEvent(event);
- }
- }
- }
大家可以看到如果給它一個executor(java.util.concurrent.Executor),它就可以異步支持發布事件了。佛則就是通過發送。
所以我們發送事件只需要通過ApplicationContext.publishEvent即可,沒必要再創建自己的實現了。除非有必要。
監聽器
具體代表者是:ApplicationListener
1、其繼承自JDK的EventListener,JDK要求所有監聽器將繼承它,比如我們的AWT事件體系也是繼承自它;
2、ApplicationListener接口:
Java代碼

- public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
- void onApplicationEvent(E event);
- }
其只提供了onApplicationEvent方法,我們需要在該方法實現內部判斷事件類型來處理,也沒有提供按順序觸發監聽器的語義,所以Spring提供了另一個接口,SmartApplicationListener:
Java代碼

- public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
- //如果實現支持該事件類型 那么返回true
- boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
-
- //如果實現支持“目標”類型,那么返回true
- boolean supportsSourceType(Class<?> sourceType);
-
- //順序,即監聽器執行的順序,值越小優先級越高
- int getOrder();
- }
該接口可方便實現去判斷支持的事件類型、目標類型,及執行順序。
Spring事件機制的簡單例子
本例子模擬一個給多個人發送內容(類似于報紙新聞)的例子。
1、定義事件
Java代碼

- package com.sishuok.hello;
- import org.springframework.context.ApplicationEvent;
- public class ContentEvent extends ApplicationEvent {
- public ContentEvent(final String content) {
- super(content);
- }
- }
非常簡單,如果用戶發送內容,只需要通過構造器傳入內容,然后通過getSource即可獲取。
2、定義無序監聽器
之所以說無序,類似于AOP機制,順序是無法確定的。
Java代碼

- package com.sishuok.hello;
- import org.springframework.context.ApplicationEvent;
- import org.springframework.context.ApplicationListener;
- import org.springframework.stereotype.Component;
- @Component
- public class LisiListener implements ApplicationListener<ApplicationEvent> {
- @Override
- public void onApplicationEvent(final ApplicationEvent event) {
- if(event instanceof ContentEvent) {
- System.out.println("李四收到了新的內容:" + event.getSource());
- }
- }
- }
1、使用@Compoent注冊Bean即可;
2、在實現中需要判斷event類型是ContentEvent才可以處理;
更簡單的辦法是通過泛型指定類型,如下所示
Java代碼

- package com.sishuok.hello;
- import org.springframework.context.ApplicationListener;
- import org.springframework.stereotype.Component;
- @Component
- public class ZhangsanListener implements ApplicationListener<ContentEvent> {
- @Override
- public void onApplicationEvent(final ContentEvent event) {
- System.out.println("張三收到了新的內容:" + event.getSource());
- }
- }
3、定義有序監聽器
實現SmartApplicationListener接口即可。
Java代碼

- package com.sishuok.hello;
- import org.springframework.context.ApplicationEvent;
- import org.springframework.context.event.SmartApplicationListener;
- import org.springframework.stereotype.Component;
-
- @Component
- public class WangwuListener implements SmartApplicationListener {
-
- @Override
- public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
- return eventType == ContentEvent.class;
- }
- @Override
- public boolean supportsSourceType(final Class<?> sourceType) {
- return sourceType == String.class;
- }
- @Override
- public void onApplicationEvent(final ApplicationEvent event) {
- System.out.println("王五在孫六之前收到新的內容:" + event.getSource());
- }
- @Override
- public int getOrder() {
- return 1;
- }
- }
Java代碼

- package com.sishuok.hello;
- import org.springframework.context.ApplicationEvent;
- import org.springframework.context.event.SmartApplicationListener;
- import org.springframework.stereotype.Component;
-
- @Component
- public class SunliuListener implements SmartApplicationListener {
-
- @Override
- public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
- return eventType == ContentEvent.class;
- }
-
- @Override
- public boolean supportsSourceType(final Class<?> sourceType) {
- return sourceType == String.class;
- }
-
- @Override
- public void onApplicationEvent(final ApplicationEvent event) {
- System.out.println("孫六在王五之后收到新的內容:" + event.getSource());
- }
-
- @Override
- public int getOrder() {
- return 2;
- }
- }
- supportsEventType:用于指定支持的事件類型,只有支持的才調用onApplicationEvent;
- supportsSourceType:支持的目標類型,只有支持的才調用onApplicationEvent;
- getOrder:即順序,越小優先級越高
4、測試
4.1、配置文件
Java代碼

- <context:component-scan base-package="com.sishuok"/>
就一句話,自動掃描注解Bean。
4.2、測試類
Java代碼

- package com.sishuok;
- import com.sishuok.hello.ContentEvent;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.ApplicationContext;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations={"classpath:spring-config-hello.xml"})
- public class HelloIT {
-
- @Autowired
- private ApplicationContext applicationContext;
- @Test
- public void testPublishEvent() {
- applicationContext.publishEvent(new ContentEvent("今年是龍年的博客更新了"));
- }
-
- }
接著會輸出:
Java代碼

- 王五在孫六之前收到新的內容:今年是龍年的博客更新了
- 孫六在王五之后收到新的內容:今年是龍年的博客更新了
- 李四收到了新的內容:今年是龍年的博客更新了
- 張三收到了新的內容:今年是龍年的博客更新了
一個簡單的測試例子就演示完畢,而且我們使用spring的事件機制去寫相關代碼會非常簡單。
Spring事件機制實現之前提到的注冊流程
具體請下載源代碼參考com.sishuok.register包里的代碼。此處貼一下源碼結構:

這里講解一下Spring對異步事件機制的支持,實現方式有兩種:
1、全局異步
即只要是觸發事件都是以異步執行,具體配置(spring-config-register.xml)如下:
Java代碼

- <task:executor id="executor" pool-size="10" />
- <!-- 名字必須是applicationEventMulticaster和messageSource是一樣的,默認找這個名字的對象 -->
- <!-- 名字必須是applicationEventMulticaster,因為AbstractApplicationContext默認找個 -->
- <!-- 如果找不到就new一個,但不是異步調用而是同步調用 -->
- <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
- <!-- 注入任務執行器 這樣就實現了異步調用(缺點是全局的,要么全部異步,要么全部同步(刪除這個屬性即是同步)) -->
- <property name="taskExecutor" ref="executor"/>
- </bean>
通過注入taskExecutor來完成異步調用。具體實現可參考之前的代碼介紹。這種方式的缺點很明顯:要么大家都是異步,要么大家都不是。所以不推薦使用這種方式。