一、代碼實例
回到第IOC的第七章context部分,我們看源碼分析部分,可以看到在spring的bean加載之后的第二個重要的bean為applicationEventMulticaster,從字面上我們知道它是一個事件廣播器。在第8和9部分,詳細描述了廣播器的初始化:
1、查找是否有name為applicationEventMulticaster的bean,如果有放到容器里,如果沒有,初始化一個系統默認的放入容器
2、查找手動設置的applicationListeners,添加到applicationEventMulticaster里
3、查找定義的類型為ApplicationListener的bean,設置到applicationEventMulticaster
4、初始化完成、對earlyApplicationEvents里的事件進行通知(此容器僅僅是廣播器未建立的時候保存通知信息,一旦容器建立完成,以后均直接通知)
5、在系統操作時候,遇到的各種bean的通知事件進行通知
以上流程我們在第七章源碼部分都已經分析過了,所以不再贅述。可以看到的是applicationEventMulticaster是一個標准的觀察者模式,對於他內部的監聽者applicationListeners,每次事件到來都會一一獲取通知。
我們來進行實例展示:
1、定義一個ApplicationEvent,
package com.zjl; import org.springframework.context.ApplicationEvent; public class MyApplicationEvent extends ApplicationEvent { /** * */ private static final long serialVersionUID = 1L; public MyApplicationEvent(Object source) { super(source); } }
2、定義一個事件處理器MyApplicationListener,僅僅監聽我們自定義的事件,注:此處的泛型如果不指定或者指定ApplicationEvent,可以監聽所有spring發出的監聽
package com.zjl; import org.springframework.context.ApplicationListener; public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { System.out.println(event.getSource()+"==== "+event.getTimestamp()); } }
3、測試代碼
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); context.publishEvent(new MyApplicationEvent("init-bean")); Person person=(Person)context.getBean("person"); person.sayHello(); } }
4、結果(成功的打印出事件的內容)
init-bean==== 1462785633657
hello zhangsan
二、源碼分析
1、執行publishEvent,支持兩種事件1、直接繼承ApplicationEvent,2、其他時間,會被包裝為PayloadApplicationEvent,可以使用getPayload獲取真實的通知內容
如果廣播器未生成,先存起來,已經生成的調用multicastEvent進行發送
protected void publishEvent(Object event, ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); if (logger.isTraceEnabled()) { logger.trace("Publishing event in " + getDisplayName() + ": " + event); } // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; //支持兩種事件1、直接繼承ApplicationEvent,2、其他時間,會被包裝為PayloadApplicationEvent,可以使用getPayload獲取真實的通知內容 if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<Object>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType(); } } // Multicast right now if possible - or lazily once the multicaster is initialized if (this.earlyApplicationEvents != null) { //如果有預制行添加到預制行,預制行在執行一次后被置為null,以后都是直接執行 this.earlyApplicationEvents.add(applicationEvent); } else { //廣播event時間 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well... //父bean同樣廣播 if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
2、查找所有的監聽者,依次遍歷,如果有線程池,利用線程池進行發送,如果沒有則直接發送,如果針對比較大的並發量,我們應該采用線程池模式,將發送通知和真正的業務邏輯進行分離
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @Override public void run() { invokeListener(listener, event); } }); } else { invokeListener(listener, event); } } }
3、在獲取監聽器的過程中調用,,可以看到監聽器根據兩種類型判斷是否需要處理:
a)、繼承SmartApplicationListener的情況下,根據supportsEventType返回結果判斷
b)、根據監聽器的泛型判斷(示例采用第二種方式),比較泛型的代碼很復雜,暫時就不關心了
public boolean supportsEventType(ResolvableType eventType) { //判斷監聽器是否可以監聽事件有兩種方式: //1、如果監聽器是SmartApplicationListener的實現,那么會重寫supportsEventType,返回true就是支持 if (this.delegate instanceof SmartApplicationListener) { Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass(); return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass); } else { //根據泛型內容與事件類型是否一致 return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType)); } }
3、調用invokeListener,如果有errorHandler會有errorHandler處理異常
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { listener.onApplicationEvent(event); } catch (Throwable err) { errorHandler.handleError(err); } } else { try { listener.onApplicationEvent(event); } catch (ClassCastException ex) { // Possibly a lambda-defined listener which we could not resolve the generic event type for LogFactory.getLog(getClass()).debug("Non-matching event type for listener: " + listener, ex); } } }
4、到此為止,我們完成了廣播器的代碼跟蹤
三、總結
廣播器的代碼不算復雜,使用了一個標准的觀察者模式,系統在初始化的時候會在context中注冊一個applicationListeners,如果系統有需要廣播的情況下,會發送一個applicationEvent事件,注冊的listener會根據自己關心的類型進行接收和解析。
在我們之前的實力中,我們需要補充的主要有三點:
1、監聽器對於事件的處理,系統中有兩種方式:
a)、繼承SmartApplicationListener的情況下,根據supportsEventType返回結果判斷
b)、根據監聽器的泛型判斷
c)、我們很容易想到,如果沒有繼承父類,也沒有泛型的情況下,我們可以在廣播器的onApplicationEvent方法中獲取到event,然后自行過濾
2、事件通知往往是獨立於整個程序運行之外的一個補充,所以另外開啟線程進行工作是個不錯的辦法,listener中提供了executor的注入來實現多線程
3、事件的類型不僅僅支持ApplicationEvent類型,也支持其他類型,spring會自動轉化為PayloadApplicationEvent,我們調用它的getPayload將獲取到具體的數據
4、可以注入一個errorHandler,完成對異常的處理
四、示例修改
1、修改配置文件
<bean name="MyApplicationListener" class="com.zjl.MyApplicationListener"> </bean> <!-- 定義一個固定大小的線程,采用factory-method和靜態方法的形式,參數注入使用構造函數注入 --> <bean name="executor" class="java.util.concurrent.Executors" factory-method="newFixedThreadPool"> <constructor-arg index="0"><value>5</value></constructor-arg> </bean> <!-- 定義applicationEventMulticaster,注入線程池和errorHandler,此處使用系統自帶的廣播器,也可以注入其他廣播器, --> <bean name="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster"> <property name="taskExecutor" ref="executor"></property> <property name="errorHandler" ref="errorHandler"></property> </bean> <!-- 定義一個errorHandler,統一處理異常信息 --> <bean name="errorHandler" class="com.zjl.MyErrorHandler"></bean>
2、異常處理的errorHandler
public class MyErrorHandler implements ErrorHandler { @Override public void handleError(Throwable t) { System.out.println("捕獲到了異常:"+t.getMessage()); } }
3、修改MyApplicationListener ,使它繼承SmartApplicationListener,主要做以下處理:
a)supportsSourceType-判斷廣播來源類的類型,全部返回true,表示接收所有類的廣播
b)supportsEventType-接收PayloadApplicationEvent類型,也就是非event類型的廣播;自定義的廣播
c)為了使errorHandler起作用,用了一個1/0,使系統拋出一個異常,注:此處由於onApplicationEvent本身接口沒有拋出異常,所以顯式的異常系統編譯都會有問題,所以感覺並不好用
public class MyApplicationListener implements SmartApplicationListener{ @Override public void onApplicationEvent(ApplicationEvent event){ System.out.println(Thread.currentThread().getName()+"-"+event.getSource()+"===="+event.getTimestamp()); if(event.getSource().equals("person-sayhello")){ int num=1/0; } } @Override public int getOrder() { return 0; } @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){ if(PayloadApplicationEvent.class.isAssignableFrom(eventType)){ System.out.println("非ApplicationEvent的廣播"); return true; }else if(MyApplicationEvent.class.isAssignableFrom(eventType)) { System.out.println("自定義廣播"); return true; } return false; } @Override public boolean supportsSourceType(Class<?> sourceType) { System.out.println("sourceType====="+sourceType); return true; } }
4、bean內容,因為在bean中發出廣播,需要使用context,此處直接使用ApplicationContextAware接口形式注入bean
public class Person implements ApplicationContextAware { private ApplicationContext applicationContext; private String name; public boolean running; public String getName() { return name; } public void setName(String name) { this.name = name; } public void sayHello(){ applicationContext.publishEvent(new MyApplicationEvent("person-sayhello")); System.out.println("hello "+this.name); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext=applicationContext; } }
5、測試程序
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); context.publishEvent("init-bean"); Person person=(Person)context.getBean("person"); person.sayHello(); } }
6、運行結果我們可以看到不同的廣播按照規則進行了過濾,而且最終是使用不同的線程進行發送。如果有異常,errorHandler順利捕捉到了異常。那么這個例子基本涵蓋了廣播的所有點。
非ApplicationEvent的廣播 //非系統廣播自定義廣播可以通過 sourceType=====class org.springframework.context.support.ClassPathXmlApplicationContext //來自context的廣播可以通過 自定義廣播 //自定義廣播可以通過 sourceType=====class java.lang.String //來自主程序的廣播可以通過 hello zhangsan pool-1-thread-2-person-sayhello====1462862846700 //非event廣播內容 捕獲到了異常:/ by zero//異常 pool-1-thread-1-org.springframework.context.support.ClassPathXmlApplicationContext@48503868: startup date [Tue May 10 14:47:25 CST 2016]; root of context hierarchy====1462862846699 //自定義廣播