SpringBoot源码初学者(二):SpringBoot事件监听器
ps:真正適合閱讀源碼的新手來看的SpringBoot源碼講解,如果你真的想讀懂SpringBoot源碼,可以按照以下推薦的方式來閱讀文章
- 打開ide,打開SpringBoot源碼,跟著文章一起寫注釋,寫自己的注釋
- 不要過于糾結(jié)沒講到的地方,畢竟SpringBoot源碼那么多,想全講完是不可能的,只要跟著文章認(rèn)真閱讀,SpringBoot是如何運(yùn)行的一定可以有一個較為深刻的理解
- 文章適合通篇閱讀,不適合跳讀,跳躍性的閱讀很容易錯過重要的東西
- 同樣的如果之前的文章沒有讀過,還是最好先去看之前的文章
- 閱讀源碼必然少不了大段大段的源碼,一定要耐心,不要翻翻了事,往往是那些最長的方法中才是真正需要學(xué)習(xí)的
- 如果斷更了請用點贊、收藏、評論的方式激勵我
系列文章鏈接:
《SpringBoot源碼初學(xué)者(一):SpringBoot功能擴(kuò)展接口的使用與源碼分析》
文章目錄
-
- 一、監(jiān)聽器模式
-
- 1、監(jiān)聽器模式小demo!天氣監(jiān)聽器
- 2、黑默丁格大講堂,監(jiān)聽器模式機(jī)制講解
- 二、SpringBoot事件監(jiān)聽器的實現(xiàn)
-
- 1、ApplicationListener接口
- 2、ApplicationEventMulticaster接口
- 3、SpringBoot的7大事件
-
- (1)事件發(fā)生順序
- 4、事件監(jiān)聽器的源碼分析
-
- (1)監(jiān)聽器注冊流程
- (2)監(jiān)聽器觸發(fā)流程
- 5、自定義SpringBoot監(jiān)聽器
-
- (1)通過spring.factories注入
- (2)SpringApplication手動注入
- (3)SpringBoot的配置文件中注冊
- (4)多事件監(jiān)聽,實現(xiàn)SmartApplicationListener接口
一、監(jiān)聽器模式
??在學(xué)習(xí)的路上遵循一些原則,可以更高效的學(xué)習(xí),其中就有這么一條“循循漸進(jìn)”,在深入SpringBoot之前先要了解清楚什么是監(jiān)聽器,監(jiān)聽器是如何實現(xiàn)的,這些都是對付大魔王的神兵利器,和RPG游戲一樣打boss之前先要打小怪提升等級,爆出“屠龍寶刀”。
??伊澤瑞爾作為瓦羅拉大陸上組名的探險家在探險的路上,卻總是受到天氣的影響無法冒險,所以他拜托我?guī)退麑懸粋€軟件,輔助他關(guān)注天氣。
1、監(jiān)聽器模式小demo!天氣監(jiān)聽器
步驟1:創(chuàng)建抽象類WeatherEvent(天氣狀態(tài))
public abstract class weatherEvent{//獲取天氣狀態(tài)public abstract String getWeather();
}
步驟2:實現(xiàn)下雪和下雨事件
下雪事件
public class SnowEvent extends WeatherEvent{@Overidepublic String getWeather(){return "下雪了";}
}
下雨事件
public class RainEvent extends WeatherEvent{@Overidepublic String getWeather(){return "下雨了";}
}
步驟3:創(chuàng)建天氣監(jiān)聽器接口
public interface WeatherListener{void onWeatherEvent(WeatherEvent event);
}
步驟4:實現(xiàn)監(jiān)聽器,分別處理下雪和下雨的天氣
下雪的時候需要穿上大棉襖,帶上手套御寒
public class SnowListener implements WeatherListener{@Overridepublic void onWeatherEvent(WeatherEvent event){if(event instanceof SnowEvent){event.getWeather();System.out.println("今天下雪!請增加衣物,做好御寒保護(hù)!");}}
}
下雨的時候需要帶雨傘,穿雨鞋
public class RainListener implements WeatherListener{@Overridepublic void onWeatherEvent(WeatherEvent event){if(event instanceof RainEvent){event.getWeather();System.out.println("今天下雨!出門請帶好雨傘");}}
}
步驟5:創(chuàng)建廣播器接口
public interface EventMulticaster{//廣播事件void multicastEvent(WeatherEvent event);//添加監(jiān)聽器 void addListener(WeatherListener weaterListener);//刪除監(jiān)聽器 void removeListener(WeatherListener weaterListener);
}
步驟6:抽象類實現(xiàn)廣播接口
public abstract class AbstractEventMulticaster implements EventMulticaster{//存放監(jiān)聽器的集合,所有需要監(jiān)聽的事件都存在這里private List<WeaterListener> listenerList = new ArrayList<>();@Overridepublic void multicastEvent(WeatherEvent event){//采用模板方法,子類可以實現(xiàn)的doStart和doEnd,在調(diào)用監(jiān)聽器之前和之后分別作出擴(kuò)展//SpringBoot中有著大量相似的操作//SpringBoot中的前置處理器和后置處理器,就是這樣實現(xiàn)的doStart();//循環(huán)所有調(diào)用所有監(jiān)聽器的onWeatherEvent方法listenerList.forEach(i -> i.onWeatherEvent(event));doEnd();}@Overridepublic void addListener(WeatherListener weaterListener){listenerList.add(weaterListener);}@Overridepublic void removeListener(WeatherListener weaterListener){listenerList.remove(weaterListener);}abstract void doStart();abstract void doEnd();
}
步驟7:實現(xiàn)天氣事件的廣播
public class WeatherEventMulticaster extends AbstractEventMulticaster{@Overridevoid doStart(){System.out.println("開始廣播天氣預(yù)報!");}@Overridevoid doEnd(){System.out.println("廣播結(jié)束!Over!");}
}
步驟8:測試并觸發(fā)廣播
public class Test{public static void main(String[] args){//創(chuàng)建廣播器WeatherEventMulticaster eventMulticaster = new WeatherEventMulticaster();//創(chuàng)建監(jiān)聽器RainListener rainListener = new RainListener();SnowListener snowListener = new SnowListener();//添加監(jiān)聽器eventMulticaster.addListener(rainListener);eventMulticaster.addListener(snowListener);//觸發(fā)下雨事件eventMulticaster.multicastEvent(new RainEvent());//觸發(fā)下雪事件eventMulticaster.multicastEvent(new SnowEvent());}
}
2、黑默丁格大講堂,監(jiān)聽器模式機(jī)制講解
??伊澤瑞爾的探險活動終于不再受到天氣的騷擾了,可是他并不明白小小的玩意為什么如此神奇,多次詢問過我,可是無賴我語言貧乏,無法將如此復(fù)雜的思想表達(dá)清楚,只要求助老友黑默丁格,幫忙說明。
ps:工作中不僅要能實現(xiàn)功能,還要注重表達(dá)能力,在面試的時候能把思想表達(dá)的清楚可以拿到更高的薪資,在和測試交流的時候可以幫助測試?yán)斫鈱崿F(xiàn)原理,測試出隱藏在深處的bug,當(dāng)然作為天才程序員的大伙是沒有bug的,肯定是環(huán)境問題或者操作不當(dāng)導(dǎo)致的。
黑默丁格拿到代碼,簡單看了兩眼就分析出了各個模塊的作用:
- 事件:步驟1和步驟2,通過對天氣進(jìn)行抽象,并實現(xiàn)下雨和下雪的天氣狀態(tài)
- 監(jiān)聽器:步驟3和步驟4,規(guī)范對天氣監(jiān)聽的模式,并且規(guī)范對應(yīng)天氣下,需要如何處理
- 廣播器:步驟5、步驟6和步驟7,當(dāng)有事件發(fā)生的時候,廣播器發(fā)出信號,告知所有的監(jiān)聽器,監(jiān)聽器根據(jù)事件作出相應(yīng)的處理。觸發(fā)下雨事件的時候,下雨監(jiān)聽器收到消息,它抬頭一看烏云密布電閃雷鳴,微微一愣,大喊一句:“打雷下雨收衣服啊!!”,廣播器繼續(xù)通知下一個監(jiān)聽器下雪監(jiān)聽器,下雪監(jiān)聽器看看天空,擺擺手,說:“這事與我無關(guān)去找別人”
- 觸發(fā)機(jī)制:步驟8,demo中采用的硬編碼的形式觸發(fā)的,在實際運(yùn)用中,可能是濕度儀檢測到濕度暴漲開始下雨了,觸發(fā)廣播。
??在23種設(shè)計模式中是沒有監(jiān)聽器模式的,監(jiān)聽器模式是觀察者模式的一種實現(xiàn),這兩個名字都容易讓人產(chǎn)生一些誤導(dǎo),在“監(jiān)聽”、“觀察”很容易讓人覺得是監(jiān)聽器發(fā)現(xiàn)了事件,然后行動。實際上是廣播器把事件推送給所有的監(jiān)聽器,每個監(jiān)聽器都對事件做出判斷和處理。
二、SpringBoot事件監(jiān)聽器的實現(xiàn)
1、ApplicationListener接口
??ApplicationListener是Spring事件機(jī)制的一部分,與抽象類ApplicationEvent類配合來完成ApplicationContext的事件機(jī)制,實現(xiàn)ApplicationListener接口的類,會在SpringBoot加入到廣播器中,當(dāng)ApplicationContext觸發(fā)了一個事件,就用廣播器通知所有實現(xiàn)ApplicationListener接口的類。
//這個注解表示,當(dāng)前類只有一個方法
@FunctionalInterface
//傳入的泛型,說明這個監(jiān)聽器,需要監(jiān)聽的事件類型
//繼承的EventListener類,是個空類,主要是聲明繼承它的類是個事件監(jiān)聽器,面向?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的接口與我們實現(xiàn)的天氣監(jiān)聽器的步驟3幾乎一樣,如果理解了小demo這個類的作用肯定已經(jīng)了解的明明白白。
2、ApplicationEventMulticaster接口
??ApplicationEventMulticaster是Spring事件機(jī)制的廣播器接口,所有的廣播器都需要實現(xiàn)此接口,主要作用是管理所有的監(jiān)聽器,以及推送事件給監(jiān)聽器。
public interface ApplicationEventMulticaster {//添加一個監(jiān)聽器void addApplicationListener(ApplicationListener<?> listener);//根據(jù)beanName添加一個監(jiān)聽器void addApplicationListenerBean(String listenerBeanName);//移除一個監(jiān)聽器void removeApplicationListener(ApplicationListener<?> listener);//根據(jù)beanName移除一個監(jiān)聽器void removeApplicationListenerBean(String listenerBeanName);//移除所有監(jiān)聽器void removeAllListeners();//廣播事件的方法void multicastEvent(ApplicationEvent event);void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
3、SpringBoot的7大事件
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-cu6pwkNo-1585491460589)(en-resource://database/2523:1)]
- EventObject:事件頂級對象,所有事件對象的根對象
- ApplicationEvent:應(yīng)用事件
- SpringApplicationEvent:Spring自己的事件,Spring框架自身的事件都會實現(xiàn)這個接口
- ApplicationStartingEvent:啟動事件,框架剛剛啟動就會發(fā)出這個事件
- ApplicationEnvironmentPreparedEvent:環(huán)境在變完成,系統(tǒng)屬性和用戶指定已經(jīng)加載完成
- ApplicationContextInitializedEvent:已經(jīng)創(chuàng)建好了上下文,并且還沒有加載任何bean之前發(fā)出這個事件
- ApplicationPreparedEvent:在Bean定義開始加載之后,尚未完全加載之前,刷新上下文之前觸發(fā)
- ApplicationStartedEvent:bean已經(jīng)創(chuàng)建完成,上下文已經(jīng)刷新完成,但是ApplicationRunner和CommandLineRunne兩個擴(kuò)展接口并未執(zhí)行
- ApplicationReadyEvent:ApplicationRunner和CommandLineRunne兩個擴(kuò)展接口執(zhí)行完成之后觸發(fā)
- ApplicationFailedEvent:在啟動發(fā)生異常時觸發(fā)
(1)事件發(fā)生順序
啟動 —》ApplicationStartingEvent —》ApplicationEnvironmentPreparedEvent —》ApplicationContextInitializedEvent —》 ApplicationPreparedEvent —》ApplicationStartedEvent —》 ApplicationReadyEvent —》啟動完畢
中間發(fā)生異常 —》ApplicationFailedEvent —》啟動失敗
4、事件監(jiān)聽器的源碼分析
(1)監(jiān)聽器注冊流程
如果看過之前的文章
《 SpringBoot源碼初學(xué)者(一):SpringBoot功能擴(kuò)展接口的使用與源碼分析》:https://blog.csdn.net/qq_34886352/article/details/104949485
這里就很容易理解,不想完整的閱讀可以只看一下工廠加載機(jī)制源碼解析的部分
與ApplicationContextInitializer接口完全一樣的流程進(jìn)行注冊的,只是把ApplicationContextInitializer接口換成了ApplicationListener接口
我們還是從最開始的main方法一步步看。
步驟1:查看SpringBoot啟動類
@SpringBootApplication
public class Application {public static void main(String[] args) {//進(jìn)入run方法的源碼SpringApplication.run(Application.class, args);}
}
步驟2:這里可以看到一層簡單的調(diào)用
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {//進(jìn)入這個同名方法,繼續(xù)戳run方法return run(new Class<?>[] { primarySource }, args);
}
步驟3:這里就比較有意思了,注意一下注釋
public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {//點這個SpringApplication構(gòu)造方法return new SpringApplication(primarySources).run(args);
}
步驟4:沒有什么用的封裝,對構(gòu)成函數(shù)復(fù)用
public SpringApplication(Class<?>... primarySources) {//點this,查看構(gòu)造函數(shù)this(null, primarySources);
}
步驟5:這里我們可以看到兩個熟悉的名字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();//這里就是上一篇文章說的ApplicationContextInitializer接口注冊setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//這里就是ApplicationListener注冊的位置,可以看出主要區(qū)別就是查詢的接口類不同//setListeners是找到的對象存到容器中,存到一個list屬性中,方便以后使用//這個存放對象的list,對應(yīng)的是小demo的AbstractEventMulticaster類中l(wèi)ist,作用是一樣一樣的//getSpringFactoriesInstances方法詳解參考文章《SpringBoot功能擴(kuò)展接口的使用與源碼分析》setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}
(2)監(jiān)聽器觸發(fā)流程
步驟1:查看SpringBoot啟動類
@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) {//點擊run方法return new SpringApplication(primarySources).run(args);
}
步驟4:每次看到這個方法,都感覺它罪孽深重,多少人從它開始看起,踏上閱讀源碼的不歸路
代碼較長,這次就不寫所有的注釋了,具體注釋看這里https://blog.csdn.net/qq_34886352/article/details/104949485
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)部包含一個SpringApplicationRunListener(這里s沒有了)的集合//SpringApplicationRunListener有7大事件的執(zhí)行方法,在對應(yīng)的地點會被調(diào)用,SpringBoot通過這個實現(xiàn)事件的觸發(fā)//SpringBoot自帶一個實現(xiàn),這個實現(xiàn)分別會執(zhí)行定義好的7大事件//使用者可以通過實現(xiàn)SpringApplicationRunListener的接口,定義在對應(yīng)事件所需執(zhí)行的命令//總體流程還是很簡單的,留給大家自己閱讀SpringApplicationRunListeners listeners = getRunListeners(args);//監(jiān)聽器的故事從這里開始,我們這次的故事也從這里起航//進(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:沒有千層套路
public void starting() {//listeners里面存放了所有的SpringApplicationRunListener(事件觸發(fā)器)for (SpringApplicationRunListener listener : this.listeners) {//循環(huán)執(zhí)行事件觸發(fā)器的starting方法//點擊進(jìn)入看看SpringBoot自帶的事件觸發(fā)器是如何運(yùn)行的listener.starting();}
}
步驟6:廣播器發(fā)送事件
@Overridepublic void starting() {//initialMulticaster是廣播器this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}
步驟7:廣播器發(fā)送事件
@Overridepublic void starting() {//initialMulticaster是廣播器//進(jìn)入multicastEvent方法this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}
步驟8:廣播事件的時候要判斷這個事件的類型,判斷需不需要在這個時間點執(zhí)行
@Override
public void multicastEvent(ApplicationEvent event) {//resolveDefaultEventType方法,解析事件的默認(rèn)類型//進(jìn)入resolveDefaultEventType方法,步驟9//進(jìn)入multicastEvent方法,步驟11multicastEvent(event, resolveDefaultEventType(event));
}
步驟9:獲取事件類型
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {//獲取事件類型//進(jìn)入forInstance方法,步驟10return ResolvableType.forInstance(event);
}
步驟10:通過接口判斷時間類型
public static ResolvableType forInstance(Object instance) {//斷路判斷,如果instance是個空,就停止SpringBoot的啟動,并報錯Assert.notNull(instance, "Instance must not be null");//判斷有沒有實現(xiàn)ResolvableTypeProvider這個接口//ResolvableTypeProvider接口,表明這個類的事件類型可以被解析if (instance instanceof ResolvableTypeProvider) {//強(qiáng)轉(zhuǎn)成ResolvableTypeProvider類型,然后獲取事件類型ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();if (type != null) {//事件類型不為空,就直接返回return type;}}//返回一個默認(rèn)類型,傳進(jìn)來的instance是什么類型,就把這個類型包裝成ResolvableType,然后返回//返回步驟8return ResolvableType.forClass(instance.getClass());
}
步驟11:開始廣播
兩個參數(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í)行的線程池//如果沒有特別指定,返回為null,SpringBoot這里就是空的Executor executor = getTaskExecutor();//getApplicationListeners方法,獲取對這個事件感興趣的監(jiān)聽器//點擊進(jìn)入getApplicationListeners方法,進(jìn)入步驟12for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {//在指定線程上執(zhí)行觸發(fā)executor.execute(() -> invokeListener(listener, event));}else {//默認(rèn)方式執(zhí)行觸發(fā)invokeListener(listener, event);}}
}
步驟12:獲取對這個事件感興趣的監(jiān)聽器(緩存獲取邏輯)
參數(shù)說明:
event:當(dāng)前發(fā)生的事件,這個方法就是找到對這個事件感興趣的監(jiān)聽器
eventType:事件類型
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {//獲取事件發(fā)生的源頭類,這里就是SpringApplicationObject source = event.getSource();//獲取原頭類的類型Class<?> sourceType = (source != null ? source.getClass() : null);//獲取緩存的keyListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);//快速執(zhí)行,從緩存中獲取監(jiān)聽器,如果這個方法已經(jīng)執(zhí)行了過了,就不要在獲取一次了,直接拿到緩存ListenerRetriever retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {//返回對當(dāng)前事件感興趣的監(jiān)聽器return retriever.getApplicationListeners();}if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {//通過key上鎖,這是上鎖的一個很有效的方式,定義一個屬性作為鎖的keysynchronized (this.retrievalMutex) {//上鎖之后再次檢查,有沒有其他地方觸發(fā)了當(dāng)前事件,把監(jiān)聽器的列表放入了緩存中//寫過雙層驗證的單例模式對這里不會陌生,主要原理是一樣的retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {//返回對當(dāng)前事件感興趣的監(jiān)聽器return retriever.getApplicationListeners();}retriever = new ListenerRetriever(true);//真正的查找邏輯被封裝在這里//SpringBoot這種千層套路,是有規(guī)律可循的,這一次是緩存的封裝,下一次是實際的調(diào)用//我們編程的時候可以學(xué)習(xí)一下,比如封裝緩存的查詢,再去數(shù)據(jù)庫,降低耦合度//點retrieveApplicationListeners方法進(jìn)入 步驟13Collection<ApplicationListener<?>> listeners =retrieveApplicationListeners(eventType, sourceType, retriever);//存入緩存中this.retrieverCache.put(cacheKey, retriever);return listeners;}}else {//不需要加鎖的,并且不需要緩存的查詢方式//這個方法中有兩處調(diào)用了retrieveApplicationListeners方法,在方法的內(nèi)部對有無緩存,做了不同的處理//個人觀點:應(yīng)該把內(nèi)部的緩存邏輯移到這層中,否則耦合度依舊很高return retrieveApplicationListeners(eventType, sourceType, null);}
}
步驟13:真正獲取監(jiān)聽器的邏輯
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)聽器實例listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);//獲取所有監(jiān)聽器的beanNamelistenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}//對所有的監(jiān)聽器進(jìn)行逐一的循環(huán)for (ApplicationListener<?> listener : listeners) {//判斷監(jiān)聽器是否對這個事件感興趣//點擊supportsEvent方法進(jìn)入 步驟14if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {//如果監(jiān)聽器功能開啟了緩存,就存到緩存中retriever.applicationListeners.add(listener);}//不管有沒有緩存都會存到這里allListeners.add(listener);}}//通過工廠方式,獲取監(jiān)聽器,一般情況不會走這里if (!listenerBeans.isEmpty()) {//獲取bean工廠BeanFactory beanFactory = getBeanFactory();//循環(huán)監(jiān)聽器beanNamefor (String listenerBeanName : listenerBeans) {try {//更具beanName,獲取監(jiān)聽器的類型Class<?> listenerType = beanFactory.getType(listenerBeanName);// 判斷監(jiān)聽器是否對這個事件感興趣if (listenerType == null || supportsEvent(listenerType, eventType)) {//獲取bean實例,這個方法寫作getBean,讀作createBean//這是ioc中非常重要的一塊邏輯,當(dāng)獲取不到bean的時候,就會創(chuàng)建一個bean對象//具體的我們在后續(xù)ioc源碼分析的時候講解ApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class);if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {//也是判斷是否有緩存的邏輯if (retriever != null) {//多一個判斷是否單例的邏輯if (beanFactory.isSingleton(listenerBeanName)) {retriever.applicationListeners.add(listener);}else {//原形bean這里,想起來以前有個組員說這個叫“多例”,最好還是叫“原型”retriever.applicationListenerBeans.add(listenerBeanName);}}allListeners.add(listener);}}}catch (NoSuchBeanDefinitionException ex) {}}}//進(jìn)行排序,SpringBoot的常規(guī)操作了,根據(jù)Order接口或者注解進(jìn)行排序AnnotationAwareOrderComparator.sort(allListeners);//對緩存進(jìn)行一次刷新,把以前的結(jié)果清空,將這次運(yùn)行的結(jié)果緩存if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {retriever.applicationListeners.clear();retriever.applicationListeners.addAll(allListeners);}//返回獲取到的監(jiān)聽器//返回 步驟12return allListeners;
}
步驟14:判斷監(jiān)聽器是否對當(dāng)前事件感興趣
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {//判斷監(jiān)聽器,是否是GenericApplicationListener的子類//starting的事件并不是其子類//GenericApplicationListener使用了裝飾器模式//著名的裝飾器模式是java中io流(inputStream這些)//GenericApplicationListener中可以解析ApplicationListener接口中的泛型參數(shù),接口如下://“ApplicationListener<E extends ApplicationEvent>”要是還想不起來,回頭看一下上面小Demo中的使用,和對這個接口的介紹GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));//下面就變得簡單了,雖然內(nèi)部的判斷很繁雜,總體只做了兩件事情//supportsEventType:判斷監(jiān)聽器是否支持當(dāng)前事件//supportsSourceType:監(jiān)聽器是否對這個事件的發(fā)起來類感興趣//返回一個總的bool值,返回 步驟13return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}
5、自定義SpringBoot監(jiān)聽器
(1)通過spring.factories注入
步驟1:創(chuàng)建監(jiān)聽器,并實現(xiàn)ApplicationListener接口
//我們讓這個監(jiān)聽器對ApplicationStartedEvent事件感興趣
@Order(1)
public class TestListener implements ApplicationListener<ApplicationStartedEvent>{@Ovrridepublic void onApplicationEvent(ApplicationStartedEvent event){System.out.println("hello, Application start is over");}
}
步驟2:在spring.factories中添加實現(xiàn)類的指引
這里涉及上一講的內(nèi)容,還不會的小伙伴們猛戳這里,趕緊補(bǔ)習(xí)一下:
https://blog.csdn.net/qq_34886352/article/details/104949485
#com.gyx.test.Listener是剛剛寫的監(jiān)聽器的全路徑名
org.springframework.context.ApplicationListener=com.gyx.test.TestListener
然后運(yùn)行程序,就可以發(fā)現(xiàn)打印的語句出現(xiàn)了
(2)SpringApplication手動注入
步驟1:創(chuàng)建監(jiān)聽器,并實現(xiàn)ApplicationListener接口,和上面的完全一樣
步驟2:修改SpringBoot啟動類
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(Application.class);//添加到初始化配置項中springApplication.addListeners(new TestListener());springApplication.run(args);}
}
(3)SpringBoot的配置文件中注冊
步驟1:創(chuàng)建監(jiān)聽器,并實現(xiàn)ApplicationListener接口,和上面的完全一樣
步驟2:修改配置文件
context.listener.classes=com.gyx.test.TestListener
看過上一課的小伙伴們,是不是發(fā)現(xiàn)了,和之前ApplicationContextInitializer的注冊方式完全一樣!!!是不是有點感覺了,趁熱打鐵趕緊吧上一講再去回顧一下吧
(4)多事件監(jiān)聽,實現(xiàn)SmartApplicationListener接口
這種方法只是實現(xiàn)的接口不一樣,注入的方式是一樣的,上面的三種注入方式都可以使用
步驟1:創(chuàng)建監(jiān)聽器,并實現(xiàn)SmartApplicationListener接口
@Order(1)
public class TestSmartListener implements SmartApplicationListener{@Ovrridepublic boolean supportsEventType(Class<? extends ApplicationEvent> eventType){//這里是類型判斷,判斷監(jiān)聽器感興趣的事件//可以對多個事件感興趣,這里就配置了兩個事件return ApplicationStartedEvent.class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType);}@Ovrridepublic void onApplicationEvent(ApplicationStartedEvent event){System.out.println("hello, This is smartApplicationListener");}
}
總結(jié)
以上是生活随笔為你收集整理的SpringBoot源码初学者(二):SpringBoot事件监听器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php怎么上传函数,PHP单文件上传原理
- 下一篇: ES6 Reflect使用笔记