[spring源碼學習]9、IOC源碼-applicationEventMulticaster事件廣播

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 //自定義廣播
    
相關文章
相關標籤/搜索