1、代碼實例java
回到第IOC的第七章context部分,咱們看源碼分析部分,能夠看到在spring的bean加載以後的第二個重要的bean爲applicationEventMulticaster,從字面上咱們知道它是一個事件廣播器。在第8和9部分,詳細描述了廣播器的初始化:spring
一、查找是否有name爲applicationEventMulticaster的bean,若是有放到容器裏,若是沒有,初始化一個系統默認的放入容器多線程
二、查找手動設置的applicationListeners,添加到applicationEventMulticaster裏併發
三、查找定義的類型爲ApplicationListener的bean,設置到applicationEventMulticasterapp
四、初始化完成、對earlyApplicationEvents裏的事件進行通知(此容器僅僅是廣播器未創建的時候保存通知信息,一旦容器創建完成,之後均直接通知)ide
五、在系統操做時候,遇到的各類bean的通知事件進行通知函數
以上流程咱們在第七章源碼部分都已經分析過了,因此再也不贅述。能夠看到的是applicationEventMulticaster是一個標準的觀察者模式,對於他內部的監聽者applicationListeners,每次事件到來都會一一獲取通知。源碼分析
咱們來進行實例展現:測試
一、定義一個ApplicationEvent,this
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); } }
二、定義一個事件處理器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()); } }
三、測試代碼
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(); } }
四、結果(成功的打印出事件的內容)
init-bean==== 1462785633657
hello zhangsan
2、源碼分析
一、執行publishEvent,支持兩種事件一、直接繼承ApplicationEvent,二、其餘時間,會被包裝爲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; //支持兩種事件一、直接繼承ApplicationEvent,二、其餘時間,會被包裝爲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); } } }
二、查找全部的監聽者,依次遍歷,若是有線程池,利用線程池進行發送,若是沒有則直接發送,若是針對比較大的併發量,咱們應該採用線程池模式,將發送通知和真正的業務邏輯進行分離
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); } } }
三、在獲取監聽器的過程當中調用,,能夠看到監聽器根據兩種類型判斷是否須要處理:
a)、繼承SmartApplicationListener的狀況下,根據supportsEventType返回結果判斷
b)、根據監聽器的泛型判斷(示例採用第二種方式),比較泛型的代碼很複雜,暫時就不關心了
public boolean supportsEventType(ResolvableType eventType) { //判斷監聽器是否能夠監聽事件有兩種方式: //一、若是監聽器是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)); } }
三、調用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); } } }
四、到此爲止,咱們完成了廣播器的代碼跟蹤
3、總結
廣播器的代碼不算複雜,使用了一個標準的觀察者模式,系統在初始化的時候會在context中註冊一個applicationListeners,若是系統有須要廣播的狀況下,會發送一個applicationEvent事件,註冊的listener會根據本身關心的類型進行接收和解析。
在咱們以前的實力中,咱們須要補充的主要有三點:
一、監聽器對於事件的處理,系統中有兩種方式:
a)、繼承SmartApplicationListener的狀況下,根據supportsEventType返回結果判斷
b)、根據監聽器的泛型判斷
c)、咱們很容易想到,若是沒有繼承父類,也沒有泛型的狀況下,咱們能夠在廣播器的onApplicationEvent方法中獲取到event,而後自行過濾
二、事件通知每每是獨立於整個程序運行以外的一個補充,因此另外開啓線程進行工做是個不錯的辦法,listener中提供了executor的注入來實現多線程
三、事件的類型不只僅支持ApplicationEvent類型,也支持其餘類型,spring會自動轉化爲PayloadApplicationEvent,咱們調用它的getPayload將獲取到具體的數據
四、能夠注入一個errorHandler,完成對異常的處理
4、示例修改
一、修改配置文件
<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>
二、異常處理的errorHandler
public class MyErrorHandler implements ErrorHandler { @Override public void handleError(Throwable t) { System.out.println("捕獲到了異常:"+t.getMessage()); } }
三、修改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; } }
四、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; } }
五、測試程序
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(); } }
六、運行結果咱們能夠看到不一樣的廣播按照規則進行了過濾,並且最終是使用不一樣的線程進行發送。若是有異常,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 //自定義廣播