SpringBoot源碼初學(xué)者(二):SpringBoot事件監(jiān)聽(tīng)器

ps:真正適合閱讀源碼的新手來(lái)看的SpringBoot源碼講解,如果你真的想讀懂SpringBoot源碼甫男,可以按照以下推薦的方式來(lái)閱讀文章

  1. 打開(kāi)ide,打開(kāi)SpringBoot源碼,跟著文章一起寫注釋式塌,寫自己的注釋
  2. 不要過(guò)于糾結(jié)沒(méi)講到的地方劝术,畢竟SpringBoot源碼那么多缩多,想全講完是不可能的,只要跟著文章認(rèn)真閱讀养晋,SpringBoot是如何運(yùn)行的一定可以有一個(gè)較為深刻的理解
  3. 文章適合通篇閱讀衬吆,不適合跳讀,跳躍性的閱讀很容易錯(cuò)過(guò)重要的東西
  4. 同樣的如果之前的文章沒(méi)有讀過(guò)绳泉,還是最好先去看之前的文章
  5. 閱讀源碼必然少不了大段大段的源碼逊抡,一定要耐心,不要翻翻了事零酪,往往是那些最長(zhǎng)的方法中才是真正需要學(xué)習(xí)的
  6. 如果斷更了請(qǐng)用點(diǎn)贊冒嫡、收藏、評(píng)論的方式激勵(lì)我

系列文章鏈接:
《SpringBoot源碼初學(xué)者(一):SpringBoot功能擴(kuò)展接口的使用與源碼分析》

一四苇、監(jiān)聽(tīng)器模式

??在學(xué)習(xí)的路上遵循一些原則孝凌,可以更高效的學(xué)習(xí),其中就有這么一條“循循漸進(jìn)”蛔琅,在深入SpringBoot之前先要了解清楚什么是監(jiān)聽(tīng)器胎许,監(jiān)聽(tīng)器是如何實(shí)現(xiàn)的峻呛,這些都是對(duì)付大魔王的神兵利器,和RPG游戲一樣打boss之前先要打小怪提升等級(jí)辜窑,爆出“屠龍寶刀”钩述。
??伊澤瑞爾作為瓦羅拉大陸上組名的探險(xiǎn)家在探險(xiǎn)的路上,卻總是受到天氣的影響無(wú)法冒險(xiǎn)穆碎,所以他拜托我?guī)退麑懸粋€(gè)軟件牙勘,輔助他關(guān)注天氣。

1所禀、監(jiān)聽(tīng)器模式小demo方面!天氣監(jiān)聽(tīng)器

步驟1:創(chuàng)建抽象類WeatherEvent(天氣狀態(tài))

public abstract class weatherEvent{
    //獲取天氣狀態(tài)
    public abstract String getWeather();
}

步驟2:實(shí)現(xiàn)下雪和下雨事件
下雪事件

public class SnowEvent extends WeatherEvent{
    @Overide
    public String getWeather(){
        return "下雪了";
    }
}

下雨事件

public class RainEvent extends WeatherEvent{
    @Overide
    public String getWeather(){
        return "下雨了";
    }
}

步驟3:創(chuàng)建天氣監(jiān)聽(tīng)器接口

public interface WeatherListener{
    void onWeatherEvent(WeatherEvent event);
}

步驟4:實(shí)現(xiàn)監(jiān)聽(tīng)器,分別處理下雪和下雨的天氣
下雪的時(shí)候需要穿上大棉襖色徘,帶上手套御寒

public class SnowListener implements WeatherListener{
    @Override
    public void onWeatherEvent(WeatherEvent event){
        if(event instanceof SnowEvent){
            event.getWeather();
            System.out.println("今天下雪恭金!請(qǐng)?jiān)黾右挛铮龊糜Wo(hù)褂策!");
        }
    }
}

下雨的時(shí)候需要帶雨傘横腿,穿雨鞋

public class RainListener implements WeatherListener{
    @Override
    public void onWeatherEvent(WeatherEvent event){
        if(event instanceof RainEvent){
            event.getWeather();
            System.out.println("今天下雨!出門請(qǐng)帶好雨傘");
        }
    }
}

步驟5:創(chuàng)建廣播器接口

public interface EventMulticaster{
    //廣播事件
    void multicastEvent(WeatherEvent event);
    //添加監(jiān)聽(tīng)器 
    void addListener(WeatherListener weaterListener);
    //刪除監(jiān)聽(tīng)器 
    void removeListener(WeatherListener weaterListener);
}

步驟6:抽象類實(shí)現(xiàn)廣播接口

public abstract class AbstractEventMulticaster implements EventMulticaster{
    //存放監(jiān)聽(tīng)器的集合斤寂,所有需要監(jiān)聽(tīng)的事件都存在這里
    private List<WeaterListener> listenerList = new ArrayList<>();

    @Override
    public void multicastEvent(WeatherEvent event){
        //采用模板方法耿焊,子類可以實(shí)現(xiàn)的doStart和doEnd,在調(diào)用監(jiān)聽(tīng)器之前和之后分別作出擴(kuò)展
        //SpringBoot中有著大量相似的操作
        //SpringBoot中的前置處理器和后置處理器遍搞,就是這樣實(shí)現(xiàn)的
        doStart();
        //循環(huán)所有調(diào)用所有監(jiān)聽(tīng)器的onWeatherEvent方法
        listenerList.forEach(i -> i.onWeatherEvent(evnet));
        doEnd();
    }
    
    @Override
    public void addListener(WeatherListener weaterListener){
        listenerList.add(weaterListener);
    }
    
    @Override
    public void removeListener(WeatherListener weaterListener){
        listenerList.remove(weaterListener);
    }
    
    abstract void doStart();
    abstract void doEnd();
}

步驟7:實(shí)現(xiàn)天氣事件的廣播

public class WeatherEventMulticaster extends AbstractEventMulticaster{
    @Override
    void doStart(){
        System.out.println("開(kāi)始廣播天氣預(yù)報(bào)罗侯!");
    }
    
    @Override
    void doEnd(){
        System.out.println("廣播結(jié)束溪猿!Over钩杰!");
    }
}

步驟8:測(cè)試并觸發(fā)廣播

public class Test{
    public static void main(String[] args){
        //創(chuàng)建廣播器
        WeatherEventMulticaster eventMulticaster = new WeatherEventMulticaster();
        //創(chuàng)建監(jiān)聽(tīng)器
        RainListener rainListener = new RainListener();
        SnowListener snowListener = new SnowListener();
        //添加監(jiān)聽(tīng)器
        eventMulticaster.addListener(rainListener);
        eventMulticaster.addListener(snowListener);
        
        //觸發(fā)下雨事件
        eventMulticaster.multicastEvent(new RainEvent());
        //除非下雪事件
        eventMulticaster.multicastEvent(new SnowEvent());
    }
}

2再愈、黑默丁格大講堂榜苫,監(jiān)聽(tīng)器模式機(jī)制講解

??伊澤瑞爾的探險(xiǎn)活動(dòng)終于不再受到天氣的騷擾了护戳,可是他并不明白小小的玩意為什么如此神奇翎冲,多次詢問(wèn)過(guò)我,可是無(wú)賴我語(yǔ)言貧乏媳荒,無(wú)法將如此復(fù)雜的思想表達(dá)清楚抗悍,只要求助老友黑默丁格,幫忙說(shuō)明钳枕。

ps:工作中不僅要能實(shí)現(xiàn)功能缴渊,還要注重表達(dá)能力,在面試的時(shí)候能把思想表達(dá)的清楚可以拿到更高的薪資鱼炒,在和測(cè)試交流的時(shí)候可以幫助測(cè)試?yán)斫鈱?shí)現(xiàn)原理衔沼,測(cè)試出隱藏在深處的bug,當(dāng)然作為天才程序員的大伙是沒(méi)有bug的,肯定是環(huán)境問(wèn)題或者操作不當(dāng)導(dǎo)致的指蚁。

黑默丁格拿到代碼菩佑,簡(jiǎn)單看了兩眼就分析出了各個(gè)模塊的作用:

  • 事件:步驟1和步驟2,通過(guò)對(duì)天氣進(jìn)行抽象凝化,并實(shí)現(xiàn)下雨和下雪的天氣狀態(tài)
  • 監(jiān)聽(tīng)器:步驟3和步驟4稍坯,規(guī)范對(duì)天氣監(jiān)聽(tīng)的模式,并且規(guī)范對(duì)應(yīng)天氣下搓劫,需要如何處理
  • 廣播器:步驟5瞧哟、步驟6和步驟7,當(dāng)有事件發(fā)生的時(shí)候枪向,廣播器發(fā)出信號(hào)勤揩,告知所有的監(jiān)聽(tīng)器,監(jiān)聽(tīng)器根據(jù)事件作出相應(yīng)的處理秘蛔。觸發(fā)下雨事件的時(shí)候雄可,下雨監(jiān)聽(tīng)器收到消息,它抬頭一看烏云密布電閃雷鳴缠犀,微微一愣数苫,大喊一句:“打雷下雨收衣服啊1嬉骸虐急!”,廣播器繼續(xù)通知下一個(gè)監(jiān)聽(tīng)器下雪監(jiān)聽(tīng)器滔迈,下雪監(jiān)聽(tīng)器看看天空止吁,擺擺手,說(shuō):“這事與我無(wú)關(guān)去找別人”
  • 觸發(fā)機(jī)制:步驟8燎悍,demo中采用的硬編碼的形式觸發(fā)的敬惦,在實(shí)際運(yùn)用中,可能是濕度儀檢測(cè)到濕度暴漲開(kāi)始下雨了谈山,觸發(fā)廣播俄删。

??在23種設(shè)計(jì)模式中是沒(méi)有監(jiān)聽(tīng)器模式的,監(jiān)聽(tīng)器模式是觀察者模式的一種實(shí)現(xiàn)奏路,這兩個(gè)名字都容易讓人產(chǎn)生一些誤導(dǎo)畴椰,在“監(jiān)聽(tīng)”、“觀察”很容易讓人覺(jué)得是監(jiān)聽(tīng)器發(fā)現(xiàn)了事件鸽粉,然后行動(dòng)斜脂。實(shí)際上是廣播器把事件推送給所有的監(jiān)聽(tīng)器,每個(gè)監(jiān)聽(tīng)器都對(duì)事件做出判斷和處理触机。

二帚戳、SpringBoot事件監(jiān)聽(tīng)器的實(shí)現(xiàn)

1玷或、ApplicationListener接口

??ApplicationListener是Spring事件機(jī)制的一部分,與抽象類ApplicationEvent類配合來(lái)完成ApplicationContext的事件機(jī)制片任,實(shí)現(xiàn)ApplicationListener接口的類庐椒,會(huì)在SpringBoot加入到廣播器中,當(dāng)ApplicationContext觸發(fā)了一個(gè)事件蚂踊,就用廣播器通知所有實(shí)現(xiàn)ApplicationListener接口的類约谈。

//這個(gè)注解表示,當(dāng)前類只有一個(gè)方法
@FunctionalInterface
//傳入的泛型犁钟,說(shuō)明這個(gè)監(jiān)聽(tīng)器棱诱,需要監(jiān)聽(tīng)的事件類型
//繼承的EventListener類,是個(gè)空類涝动,主要是聲明繼承它的類是個(gè)事件監(jiān)聽(tīng)器迈勋,面向?qū)ο缶幊痰乃枷塍w現(xiàn)
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

??不難發(fā)現(xiàn)ApplicationListener的接口與我們實(shí)現(xiàn)的天氣監(jiān)聽(tīng)器的步驟3幾乎一樣,如果理解了小demo這個(gè)類的作用肯定已經(jīng)了解的明明白白醋粟。

2靡菇、ApplicationEventMulticaster接口

??ApplicationEventMulticaster是Spring事件機(jī)制的廣播器接口,所有的廣播器都需要實(shí)現(xiàn)此接口米愿,主要作用是管理所有的監(jiān)聽(tīng)器厦凤,以及推送事件給監(jiān)聽(tīng)器。

public interface ApplicationEventMulticaster {
    
    //添加一個(gè)監(jiān)聽(tīng)器
    void addApplicationListener(ApplicationListener<?> listener);

    //根據(jù)beanName添加一個(gè)監(jiān)聽(tīng)器
    void addApplicationListenerBean(String listenerBeanName);

    //移除一個(gè)監(jiān)聽(tīng)器
    void removeApplicationListener(ApplicationListener<?> listener);

    //根據(jù)beanName移除一個(gè)監(jiān)聽(tīng)器
    void removeApplicationListenerBean(String listenerBeanName);

    //移除所有監(jiān)聽(tīng)器
    void removeAllListeners();

    //廣播事件的方法
    void multicastEvent(ApplicationEvent event);

    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

3育苟、SpringBoot的7大事件

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-cu6pwkNo-1585491460589)(en-resource://database/2523:1)]

  • EventObject:事件頂級(jí)對(duì)象较鼓,所有事件對(duì)象的根對(duì)象
  • ApplicationEvent:應(yīng)用事件
  • SpringApplicationEvent:Spring自己的事件,Spring框架自身的事件都會(huì)實(shí)現(xiàn)這個(gè)接口
  • ApplicationStartingEvent:?jiǎn)?dòng)事件违柏,框架剛剛啟動(dòng)就會(huì)發(fā)出這個(gè)事件
  • ApplicationEnvironmentPreparedEvent:環(huán)境在變完成博烂,系統(tǒng)屬性和用戶指定已經(jīng)加載完成
  • ApplicationContextInitializedEvent:已經(jīng)創(chuàng)建好了上下文,并且還沒(méi)有加載任何bean之前發(fā)出這個(gè)事件
  • ApplicationPreparedEvent:在Bean定義開(kāi)始加載之后漱竖,尚未完全加載之前禽篱,刷新上下文之前觸發(fā)
  • ApplicationStartedEvent:bean已經(jīng)創(chuàng)建完成,上下文已經(jīng)刷新完成馍惹,但是ApplicationRunner和CommandLineRunne兩個(gè)擴(kuò)展接口并未執(zhí)行
  • ApplicationReadyEvent:ApplicationRunner和CommandLineRunne兩個(gè)擴(kuò)展接口執(zhí)行完成之后觸發(fā)
  • ApplicationFailedEvent:在啟動(dòng)發(fā)生異常時(shí)觸發(fā)

(1)事件發(fā)生順序

啟動(dòng) —》ApplicationStartingEvent —》ApplicationEnvironmentPreparedEvent —》ApplicationContextInitializedEvent —》 ApplicationPreparedEvent —》ApplicationStartedEvent —》 ApplicationReadyEvent —》啟動(dòng)完畢

中間發(fā)生異常 —》ApplicationFailedEvent —》啟動(dòng)失敗

4躺率、事件監(jiān)聽(tīng)器的源碼分析

(1)監(jiān)聽(tīng)器注冊(cè)流程

如果看過(guò)之前的文章
《 SpringBoot源碼初學(xué)者(一):SpringBoot功能擴(kuò)展接口的使用與源碼分析》:http://www.reibang.com/p/11b38582dfa4
這里就很容易理解,不想完整的閱讀可以只看一下工廠加載機(jī)制源碼解析的部分
與ApplicationContextInitializer接口完全一樣的流程進(jìn)行注冊(cè)的讼积,只是把ApplicationContextInitializer接口換成了ApplicationListener接口

我們還是從最開(kāi)始的main方法一步步看肥照。
步驟1:查看SpringBoot啟動(dòng)類

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //進(jìn)入run方法的源碼
        SpringApplication.run(Application.class, args);
    }
}

步驟2:這里可以看到一層簡(jiǎn)單的調(diào)用

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
       //進(jìn)入這個(gè)同名方法,繼續(xù)戳run方法
        return run(new Class<?>[] { primarySource }, args);
}

步驟3:這里就比較有意思了勤众,注意一下注釋

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //點(diǎn)這個(gè)SpringApplication構(gòu)造方法
        return new SpringApplication(primarySources).run(args);
}

步驟4:沒(méi)有什么用的封裝,對(duì)構(gòu)成函數(shù)復(fù)用

public SpringApplication(Class<?>... primarySources) {
       //點(diǎn)this鲤脏,查看構(gòu)造函數(shù)
        this(null, primarySources);
}

步驟5:這里我們可以看到兩個(gè)熟悉的名字getSpringFactoriesInstances方法和ApplicationContextInitializer接口

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //這里就是上一篇文章說(shuō)的ApplicationContextInitializer接口注冊(cè)
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
       //這里就是ApplicationListener注冊(cè)的位置们颜,可以看出主要區(qū)別就是查詢的接口類不同
       //setListeners是找到的對(duì)象存到容器中吕朵,存到一個(gè)list屬性中,方便以后使用
       //這個(gè)存放對(duì)象的list窥突,對(duì)應(yīng)的是小demo的AbstractEventMulticaster類中l(wèi)ist努溃,作用是一樣一樣的
       //getSpringFactoriesInstances方法詳解參考文章《SpringBoot功能擴(kuò)展接口的使用與源碼分析》
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
}

(2)監(jiān)聽(tīng)器觸發(fā)流程

步驟1:查看SpringBoot啟動(dòng)類

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

步驟2:ConfigurableApplicationContext類

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
}

步驟3:這次進(jìn)入run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //點(diǎn)擊run方法
        return new SpringApplication(primarySources).run(args);
}

步驟4:每次看到這個(gè)方法,都感覺(jué)它罪孽深重阻问,多少人從它開(kāi)始看起梧税,踏上閱讀源碼的不歸路
代碼較長(zhǎng),這次就不寫所有的注釋了称近,具體注釋看這里http://www.reibang.com/p/11b38582dfa4

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //獲取事件運(yùn)行器
        //SpringApplicationRunListeners內(nèi)部包含一個(gè)SpringApplicationRunListener(這里s沒(méi)有了)的集合
        //SpringApplicationRunListener有7大事件的執(zhí)行方法第队,在對(duì)應(yīng)的地點(diǎn)會(huì)被調(diào)用,SpringBoot通過(guò)這個(gè)實(shí)現(xiàn)事件的觸發(fā)
        //SpringBoot自帶一個(gè)實(shí)現(xiàn)刨秆,這個(gè)實(shí)現(xiàn)分別會(huì)執(zhí)行定義好的7大事件
        //使用者可以通過(guò)實(shí)現(xiàn)SpringApplicationRunListener的接口凳谦,定義在對(duì)應(yīng)事件所需執(zhí)行的命令
        //總體流程還是很簡(jiǎn)單的,留給大家自己閱讀
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //監(jiān)聽(tīng)器的故事從這里開(kāi)始衡未,我們這次的故事也從這里起航
        //進(jìn)入starting方法
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

步驟5:沒(méi)有千層套路

public void starting() {
    //listeners里面存放了所有的SpringApplicationRunListener(事件觸發(fā)器)
   for (SpringApplicationRunListener listener : this.listeners) {
        //循環(huán)執(zhí)行事件觸發(fā)器的starting方法
        //點(diǎn)擊進(jìn)入看看SpringBoot自帶的事件觸發(fā)器是如何運(yùn)行的
      listener.starting();
   }
}

步驟6:廣播器發(fā)送事件

@Override
    public void starting() {
        //initialMulticaster是廣播器
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

步驟7:廣播器發(fā)送事件

@Override
    public void starting() {
        //initialMulticaster是廣播器
        //進(jìn)入multicastEvent方法
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

步驟8:廣播事件的時(shí)候要判斷這個(gè)事件的類型尸执,判斷需不需要在這個(gè)時(shí)間點(diǎn)執(zhí)行

@Override
public void multicastEvent(ApplicationEvent event) {
    //resolveDefaultEventType方法,解析事件的默認(rèn)類型
    //進(jìn)入resolveDefaultEventType方法缓醋,步驟9
    //進(jìn)入multicastEvent方法如失,步驟11
   multicastEvent(event, resolveDefaultEventType(event));
}

步驟9:獲取事件類型

private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
    //獲取事件類型
    //進(jìn)入forInstance方法,步驟10
    return ResolvableType.forInstance(event);
}

步驟10:通過(guò)接口判斷時(shí)間類型

public static ResolvableType forInstance(Object instance) {
    //斷路判斷送粱,如果instance是個(gè)空岖常,就停止SpringBoot的啟動(dòng),并報(bào)錯(cuò)
    Assert.notNull(instance, "Instance must not be null");
    //判斷有沒(méi)有實(shí)現(xiàn)ResolvableTypeProvider這個(gè)接口
    //ResolvableTypeProvider接口葫督,表明這個(gè)類的事件類型可以被解析
    if (instance instanceof ResolvableTypeProvider) {
        //強(qiáng)轉(zhuǎn)成ResolvableTypeProvider類型竭鞍,然后獲取事件類型
        ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();
        if (type != null) {
            //事件類型不為空,就直接返回
            return type;
        }
    }
    //返回一個(gè)默認(rèn)類型橄镜,傳進(jìn)來(lái)的instance是什么類型偎快,就把這個(gè)類型包裝成ResolvableType,然后返回
    //返回步驟8
    return ResolvableType.forClass(instance.getClass());
}

步驟11:開(kāi)始廣播
兩個(gè)參數(shù):event:需要執(zhí)行的事件 ???eventType:事件的類型

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    //如果事件類型為空洽胶,執(zhí)行resolveDefaultEventType方法(步驟9和步驟10)
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //獲取任務(wù)的執(zhí)行的線程池
    //如果沒(méi)有特別指定晒夹,返回為null,SpringBoot這里就是空的
    Executor executor = getTaskExecutor();
    //getApplicationListeners方法姊氓,獲取對(duì)這個(gè)事件感興趣的監(jiān)聽(tīng)器
    //點(diǎn)擊進(jìn)入getApplicationListeners方法丐怯,進(jìn)入步驟12
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            //在指定線程上執(zhí)行觸發(fā)
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //默認(rèn)方式執(zhí)行觸發(fā)
            invokeListener(listener, event);
        }
    }
}

步驟12:獲取對(duì)這個(gè)事件感興趣的監(jiān)聽(tīng)器(緩存獲取邏輯)
參數(shù)說(shuō)明:
event:當(dāng)前發(fā)生的事件,這個(gè)方法就是找到對(duì)這個(gè)事件感興趣的監(jiān)聽(tīng)器
eventType:事件類型

protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {
    //獲取事件發(fā)生的源頭類翔横,這里就是SpringApplication
   Object source = event.getSource();
   //獲取原頭類的類型
   Class<?> sourceType = (source != null ? source.getClass() : null);
   //獲取緩存的key
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

   //快速執(zhí)行读跷,從緩存中獲取監(jiān)聽(tīng)器,如果這個(gè)方法已經(jīng)執(zhí)行了過(guò)了禾唁,就不要在獲取一次了效览,直接拿到緩存
   ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
   if (retriever != null) {
        //返回對(duì)當(dāng)前事件感興趣的監(jiān)聽(tīng)器
      return retriever.getApplicationListeners();
   }

   if (this.beanClassLoader == null ||
         (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
               (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
      //通過(guò)key上鎖无切,這是上鎖的一個(gè)很有效的方式,定義一個(gè)屬性作為鎖的key
      synchronized (this.retrievalMutex) {
        //上鎖之后再次檢查丐枉,有沒(méi)有其他地方觸發(fā)了當(dāng)前事件哆键,把監(jiān)聽(tīng)器的列表放入了緩存中
        //寫過(guò)雙層驗(yàn)證的單例模式對(duì)這里不會(huì)陌生,主要原理是一樣的
         retriever = this.retrieverCache.get(cacheKey);
         if (retriever != null) {
            //返回對(duì)當(dāng)前事件感興趣的監(jiān)聽(tīng)器
            return retriever.getApplicationListeners();
         }
         retriever = new ListenerRetriever(true);
         //真正的查找邏輯被封裝在這里
         //SpringBoot這種千層套路瘦锹,是有規(guī)律可循的籍嘹,這一次是緩存的封裝,下一次是實(shí)際的調(diào)用
         //我們編程的時(shí)候可以學(xué)習(xí)一下弯院,比如封裝緩存的查詢辱士,再去數(shù)據(jù)庫(kù),降低耦合度
         //點(diǎn)retrieveApplicationListeners方法進(jìn)入 步驟13
         Collection<ApplicationListener<?>> listeners =
               retrieveApplicationListeners(eventType, sourceType, retriever);
         //存入緩存中
         this.retrieverCache.put(cacheKey, retriever);
         return listeners;
      }
   }
   else {
      //不需要加鎖的抽兆,并且不需要緩存的查詢方式
      //這個(gè)方法中有兩處調(diào)用了retrieveApplicationListeners方法识补,在方法的內(nèi)部對(duì)有無(wú)緩存,做了不同的處理
      //個(gè)人觀點(diǎn):應(yīng)該把內(nèi)部的緩存邏輯移到這層中辫红,否則耦合度依舊很高
      return retrieveApplicationListeners(eventType, sourceType, null);
   }
}

步驟13:真正獲取監(jiān)聽(tīng)器的邏輯

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        //獲取所有的監(jiān)聽(tīng)器實(shí)例
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        //獲取所有監(jiān)聽(tīng)器的beanName
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    //對(duì)所有的監(jiān)聽(tīng)器進(jìn)行逐一的循環(huán)
    for (ApplicationListener<?> listener : listeners) {
        //判斷監(jiān)聽(tīng)器是否對(duì)這個(gè)事件感興趣
        //點(diǎn)擊supportsEvent方法進(jìn)入  步驟14
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                //如果監(jiān)聽(tīng)器功能開(kāi)啟了緩存凭涂,就存到緩存中
                retriever.applicationListeners.add(listener);
            }
            //不管有沒(méi)有緩存都會(huì)存到這里
            allListeners.add(listener);
        }
    }
    //通過(guò)工廠方式,獲取監(jiān)聽(tīng)器贴妻,一般情況不會(huì)走這里
    if (!listenerBeans.isEmpty()) {
        //獲取bean工廠
        BeanFactory beanFactory = getBeanFactory();
        //循環(huán)監(jiān)聽(tīng)器beanName
        for (String listenerBeanName : listenerBeans) {
            try {
                //更具beanName切油,獲取監(jiān)聽(tīng)器的類型
                Class<?> listenerType = beanFactory.getType(listenerBeanName);
                // 判斷監(jiān)聽(tīng)器是否對(duì)這個(gè)事件感興趣
                if (listenerType == null || supportsEvent(listenerType, eventType)) {
                    //獲取bean實(shí)例,這個(gè)方法寫作getBean名惩,讀作createBean
                    //這是ioc中非常重要的一塊邏輯澎胡,當(dāng)獲取不到bean的時(shí)候,就會(huì)創(chuàng)建一個(gè)bean對(duì)象
                    //具體的我們?cè)诤罄m(xù)ioc源碼分析的時(shí)候講解
                    ApplicationListener<?> listener =
                            beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        //也是判斷是否有緩存的邏輯
                        if (retriever != null) {
                            //多一個(gè)判斷是否單例的邏輯
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                retriever.applicationListeners.add(listener);
                            }
                            else {
                                //原形bean這里娩鹉,想起來(lái)以前有個(gè)組員說(shuō)這個(gè)叫“多例”攻谁,最好還是叫“原型”
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
               
            }
        }
    }
    //進(jìn)行排序,SpringBoot的常規(guī)操作了弯予,根據(jù)Order接口或者注解進(jìn)行排序
    AnnotationAwareOrderComparator.sort(allListeners);
    //對(duì)緩存進(jìn)行一次刷新戚宦,把以前的結(jié)果清空,將這次運(yùn)行的結(jié)果緩存
    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
        retriever.applicationListeners.clear();
        retriever.applicationListeners.addAll(allListeners);
    }
    //返回獲取到的監(jiān)聽(tīng)器
    //返回  步驟12
    return allListeners;
}

步驟14:判斷監(jiān)聽(tīng)器是否對(duì)當(dāng)前事件感興趣

protected boolean supportsEvent(
        ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
    //判斷監(jiān)聽(tīng)器锈嫩,是否是GenericApplicationListener的子類
    //starting的事件并不是其子類
    //GenericApplicationListener使用了裝飾器模式
    //著名的裝飾器模式是java中io流(inputStream這些)
    //GenericApplicationListener中可以解析ApplicationListener接口中的泛型參數(shù)受楼,接口如下:
    //“ApplicationListener<E extends ApplicationEvent>”要是還想不起來(lái),回頭看一下上面小Demo中的使用呼寸,和對(duì)這個(gè)接口的介紹
    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
            (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    //下面就變得簡(jiǎn)單了艳汽,雖然內(nèi)部的判斷很繁雜,總體只做了兩件事情
    //supportsEventType:判斷監(jiān)聽(tīng)器是否支持當(dāng)前事件
    //supportsSourceType:監(jiān)聽(tīng)器是否對(duì)這個(gè)事件的發(fā)起來(lái)類感興趣
    //返回一個(gè)總的bool值对雪,返回  步驟13
    return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

5河狐、自定義SpringBoot監(jiān)聽(tīng)器

(1)通過(guò)spring.factories注入

步驟1:創(chuàng)建監(jiān)聽(tīng)器,并實(shí)現(xiàn)ApplicationListener接口

//我們讓這個(gè)監(jiān)聽(tīng)器對(duì)ApplicationStartedEvent事件感興趣
@Order(1)
public class TestListener implements ApplicationListener<ApplicationStartedEvent>{
    @Ovrride
    public void onApplicationEvent(ApplicationStartedEvent event){
        System.out.println("hello,  Application start is over");
    }
}

步驟2:在spring.factories中添加實(shí)現(xiàn)類的指引
這里涉及上一講的內(nèi)容,還不會(huì)的小伙伴們猛戳這里甚牲,趕緊補(bǔ)習(xí)一下:
http://www.reibang.com/p/11b38582dfa4

#com.gyx.test.Listener是剛剛寫的監(jiān)聽(tīng)器的全路徑名
org.springframework.context.ApplicationListener=com.gyx.test.TestListener

然后運(yùn)行程序义郑,就可以發(fā)現(xiàn)打印的語(yǔ)句出現(xiàn)了

(2)SpringApplication手動(dòng)注入

步驟1:創(chuàng)建監(jiān)聽(tīng)器蝶柿,并實(shí)現(xiàn)ApplicationListener接口丈钙,和上面的完全一樣
步驟2:修改SpringBoot啟動(dòng)類

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        //添加到初始化配置項(xiàng)中
        springApplication.addListeners(new TestListener());
        springApplication.run(args);
    }
}

(3)SpringBoot的配置文件中注冊(cè)

步驟1:創(chuàng)建監(jiān)聽(tīng)器,并實(shí)現(xiàn)ApplicationListener接口交汤,和上面的完全一樣
步驟2:修改配置文件

context.listener.classes=com.gyx.test.TestListener

看過(guò)上一課的小伙伴們雏赦,是不是發(fā)現(xiàn)了,和之前ApplicationContextInitializer的注冊(cè)方式完全一樣\皆P歉凇!是不是有點(diǎn)感覺(jué)了戒洼,趁熱打鐵趕緊吧上一講再去回顧一下吧

(4)多事件監(jiān)聽(tīng)俏橘,實(shí)現(xiàn)SmartApplicationListener接口

這種方法只是實(shí)現(xiàn)的接口不一樣,注入的方式是一樣的圈浇,上面的三種注入方式都可以使用
步驟1:創(chuàng)建監(jiān)聽(tīng)器寥掐,并實(shí)現(xiàn)SmartApplicationListener接口

@Order(1)
public class TestSmartListener implements SmartApplicationListener{
    @Ovrride
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
        //這里是類型判斷,判斷監(jiān)聽(tīng)器感興趣的事件
        //可以對(duì)多個(gè)事件感興趣磷蜀,這里就配置了兩個(gè)事件
        return ApplicationStartedEvent.class.isAssignableFrom(eventType) 
            || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }
    @Ovrride
    public void onApplicationEvent(ApplicationStartedEvent event){
        System.out.println("hello,  This is smartApplicationListener");
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末召耘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子褐隆,更是在濱河造成了極大的恐慌污它,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庶弃,死亡現(xiàn)場(chǎng)離奇詭異衫贬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歇攻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門固惯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人掉伏,你說(shuō)我怎么就攤上這事缝呕。” “怎么了斧散?”我有些...
    開(kāi)封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵供常,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鸡捐,道長(zhǎng)栈暇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任箍镜,我火速辦了婚禮源祈,結(jié)果婚禮上煎源,老公的妹妹穿的比我還像新娘香缺。我一直安慰自己手销,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布图张。 她就那樣靜靜地躺著锋拖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祸轮。 梳的紋絲不亂的頭發(fā)上兽埃,一...
    開(kāi)封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音适袜,去河邊找鬼柄错。 笑死,一個(gè)胖子當(dāng)著我的面吹牛苦酱,可吹牛的內(nèi)容都是我干的售貌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼躏啰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼趁矾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起给僵,我...
    開(kāi)封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毫捣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后帝际,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔓同,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年蹲诀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斑粱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脯爪,死狀恐怖则北,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痕慢,我是刑警寧澤尚揣,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站掖举,受9級(jí)特大地震影響快骗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一方篮、第九天 我趴在偏房一處隱蔽的房頂上張望名秀。 院中可真熱鬧,春花似錦藕溅、人聲如沸匕得。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耗跛。三九已至裕照,卻和暖如春攒发,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晋南。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工惠猿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人负间。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓偶妖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親政溃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趾访,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容