構(gòu)造流程源碼分析:ApplicationListener加載
ApplicationListener加載
完成了
ApplicationContextlnitializer 的加載之后,便會進行 ApplicationListener 的加載。它的常見應(yīng)用場景為:當(dāng)容器初始化完成之后,需要處理一些如數(shù)據(jù)的加載、初始化緩存、特定任務(wù)的注冊等操作。而在此階段,更多的是用于 ApplicationContext 管理 Bean 過程的場景。
Spring 事件傳播機制是基于觀察者模式(Observer) 實現(xiàn)的。比如,在 ApplicationContext管 理 Bean 生 命 周 期 的 過 程 中 , 會 將 一 些 改 變 定 義 為 事 件 ( ApplicationEvent ) 。
ApplicationContext 通過 ApplicationListener 監(jiān)聽 ApplicationEvent,當(dāng)事件被發(fā)布之后,ApplicationListener 用來對事件做出具體的操作。

ApplicationListener 的整個配置和加載流程與
ApplicationContexthnitializer 完全一致, 也是 先 通 過SpringFactoriesLoader的loadFactoryNames方 法 獲 得META-INF/spring.factories 中對應(yīng)配置,然后再進行實例化,最后將獲得的結(jié)果集合添加到SpringApplication 的成員變量 listeners 中,代碼如下。
private List> listeners;
public void setl isteners(Collection<了 extends App. lcationLi stener》>li steners)
this. listeners = new Arraylist<>(listeners); 同樣的,在調(diào)用 setListeners 方法時也會進行覆蓋賦值的操作,之前加載的內(nèi)容會被清除。
下 面 我 們 看 看 ApplicationListener 這里的基本使 用 。ApplicationListener 接 口 和ApplicationEvent 類配合使用,可實現(xiàn) ApplicationContext 的事件處理。如果容器中存在AplicationListener 的 Bean,當(dāng) ApplicationContext 調(diào)用 publishEvent 方法時,對應(yīng)的 Bean會被觸發(fā)。這就是上文提到的觀察者模式的實現(xiàn)。
在接口 ApplicationL istener 中只定義了一個 onAplicationEvent 方法,當(dāng)監(jiān)聽事件被觸發(fā)時,onApplicationEvent 方法會被執(zhí)行,接口 ApplicationListener 的源代碼如下。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends Ev
entListener
void onApplicationEvent(E event);}onApplicationEvent 方法一般用于處理應(yīng)用程序事件,參數(shù) event 為 ApplicationEvent 的子類,是具體響應(yīng)(接收到)的事件。
當(dāng) ApplicationContext 被初始化或刷新時,會觸發(fā) ContextRefreshedEvent 事件,下面我們就實現(xiàn)一-個 ApplicationListener 來監(jiān)聽此事件的發(fā)生,代碼如下。
@Component //需對該類進行 Bean 的實例化
public class LearnListener implements ApplicationListenervent>
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
/打印容中出 Beon8 得空嬰中初始化
ystem. out. println(" 監(jiān)聽器獲得容器
+ event. getApplica
tion-
Context(). getBeanDefinitionCount());
} 上面的 LearnListener 實現(xiàn)了 ApplicationListener 并監(jiān)聽 ContextRefreshedEvent 事件,當(dāng)容器創(chuàng)建或刷新時,該監(jiān)聽器的 onApplicationEvent 方法會被調(diào)用,并打印出當(dāng)前容器中 Bean 的數(shù)量。
在具體的實戰(zhàn)業(yè)務(wù)中,我們也可以自定義事件,在完成業(yè)務(wù)之后手動觸發(fā)對應(yīng)的事件監(jiān)聽器,也就是手動調(diào)用 ApplicationContext 的 publishEvent(ApplicationEvent event)方法。

入口類推斷
創(chuàng) 建 SpringApplication 的 最 后 一 步 便 是 推 斷 入 口 類 , 我 們 通 過 調(diào) 用 自 身 的
deduce-MainApplicationClass 方法來進行入口類的推斷。
private Class> deduceMainApplicationClass() {
/獲取棧元素數(shù)組
StackTraceElement[] stackTrace = new Runt imeException() . getStackTrace
()
//遍歷棧元素數(shù)組
for (StackTraceElement stackTraceElement : stackTrace) {
//匹配第一個 main 方法,并返回
("main" . equals(stackTraceElement,getMethodName())) {
return Class. forName( stackTraceElement . getClassName());
} catch (ClassNotFoundException ex) {
//如果發(fā)生異常,忽略該異常,并繼續(xù)執(zhí)行
return null;}該方法實現(xiàn)的基本流程就是先創(chuàng)建一個運行時異常, 然后獲得棧數(shù)組,遍歷棧數(shù)組,判斷類的方法中是否包含 main 方法。第-個被匹配的類會通過 Class.forName 方法創(chuàng)建對象,并 將 其 被 返 回 , 最 后 在 上 層 方 法 中 將 對 象 賦 值 給 SpringApplication 的 成 員 變 量mainApplicationClass。在遍歷過程中如果發(fā)生異常,會忽略掉該異常并繼續(xù)執(zhí)行遍歷操作。
至此,整個 SpringApplication 類的實例化過程便完成 了。

SpringApplication的定制化配置
前面我們學(xué)習(xí)了 Spring Boot 啟動過程中構(gòu)建 SpringApplication 所做的一系列初始化操作,這些操作都是 Spring Boot 默認(rèn)配置的。如果在此過程中需要定制化配置,Spring Boot 在SpringApplication 類中也提供了相應(yīng)的入口。
但正常情況下,如果無特殊需要,采用默認(rèn)配置即可。
針對定制化配置,Spring Boot 提供了如基于入口類、配置文件、環(huán)境變量、命令行參數(shù)等多種形式。下面我們了 解一下幾種不同的配置形式。
基礎(chǔ)配置
基礎(chǔ)配置與在 application.properties 文件中的配置-樣, 用來修改 SpringBoot 預(yù)置的參數(shù)。
比如,我們想在啟動程序的時候不打印 Banner 信息,可以通過在 application.properties 文件 中 設(shè) 置 “spring.main.banner-mode=off 來 進 行 關(guān) 閉 。當(dāng) 然 , 我 們 也 可 以 通 過SpringApplication 提供的相關(guān)方法來進行同樣的操作。以下是官方提供的關(guān)閉 Banner 的代碼。
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.clas
s);
app. setBannerMode( Banner .Mode.0FF);
app. run(args);
}除了前面講到的 setInitializers 和 setL isteners 方法之外,其他的 Setter 方法都具有類似的功能,比如我們可以通過 setWebApplicationType 方法來代替 Spring Boot 默認(rèn)的自動類型推斷。
針對這些 Setter 方法,SpringBoot 還專門提供了流式處理類 SpringApplicationBuilder,我們將它的功能與 SpringApplication 逐一對照,可知 SpringApplicationBuilder 的優(yōu)點是使代碼更加簡潔、流暢。
其他相同配置形式的功能就不再贅述了,我們可通過查看源代碼進行進一步的學(xué)習(xí)。出于集中配置、方便管理的思路, 不建議大家在啟動類中配置過多的參數(shù)。比如,針對 Banner的設(shè)置,我們可以在多處進行配置,但為了方便管理,盡可能的統(tǒng)一在 application.properties文件中。
配置源配置
除了直接通過 Setter 方法進行參數(shù)的配置,我們還可以通過設(shè)置配置源參數(shù)對整個配置文件或配置類進行配置。我們可通過兩個途徑進行配置:
SpringApplication 構(gòu)造方法參數(shù)或 SpringApplication 提供的 setSources 方法來進行設(shè)置。
在 3.3 節(jié) SpringApplication 構(gòu)造方法參數(shù)中已經(jīng)講到可以通過 Class...primarySources參數(shù)來配置普通類。
因此,配置類可通過 SpringApplication 的構(gòu)造方法來進行指定。但這種方法有一一個弊端就是無法指定 XML 配置和基于 package 的配置。
另外一種配置形式為直接調(diào)用 setSources 方法來進行設(shè)置,方法源代碼如下。
private Set<String> sources = new LinkedHashSet<>();
public void setSources (Set<String> sources) {
Assert. notNull(sources, "Sources must not be nul1");
this. sources = new L inkedHashSet<> ( sources);
}該方法的參數(shù)為 String 集合,可傳遞類名、package 名稱和 XML 配置資源。下面我們以類名為例進行演示。
WithoutAnnoConfiguration 配置類代碼如下。
public class WithoutAnnoConfiguration
public WithoutAnnoConfiguration(){
System. out. println( "Wi thoutAnnoConfiguration 對象被創(chuàng)建");
@Value("${ admin. name}")
private String name ;
@Value( "${admin. age}")
private int age;
//省略 getter/setter 方法
}使用該配置的實例代碼如下。
public static void main(String[] args){
SpringApplication app = new SpringApplication(SpringLearnApplication.class);
Set set = new HashSet<>();
set. add (WithoutAnnoConfiguration. class . getName());
app . setSources(set);
ConfigurableApplicat ionContext context = app . run(args);
WithoutAnnoConfiguration bean = context . getBean(WithoutAnnoConfiguration.
class);System. out. println(bean. getName());
} 運行程序,我們在日志中即可看到已經(jīng)獲取到對應(yīng)類的屬性值。
無論是通過構(gòu)造參數(shù)的形式還是通過 Setter 方法的形式對配置源信息進行指定,在 SpringBoot 中都會將其合并。SpringApplication 類中提供了 一個 getAllSources 方法,能夠?qū)烧邊?shù)進行合并。
public Set<0bject> getAllSources() {
//創(chuàng)建去除的 L inkedHashSet
Set<0bject> allSources = new LinkedHashSet<>();
// primarySources 不為空則加入 Set
if (!CollectionUtils. isEmpty(this. primarySources)) {
allSources . addAll(this . primarySources);
// sources 不為空則加入 Set
if (!CollectionUtils . isEmpty(this. sources)) {
allSources . addAll(this. sources);
//對 Set 進行包裝,變?yōu)椴豢勺兊?Set
return Collections . unmodifiableSet(allSources);}}關(guān)于 SpringApplication 類指定配置及配置源就講到這里,更多相關(guān)配置信息可參考對應(yīng)章節(jié)進行學(xué)習(xí)。
小結(jié)
本章內(nèi)容重點圍繞 SpringApplication 類的初始化過程展開,詳細(xì)介紹了在初始化過程中Spring Boot 所 進 行 的 操 作 : Web應(yīng)用類型推斷 、 入 口類 推 斷 、 默認(rèn)的
Application-Contextlnitializer 接口加載、默認(rèn)的 ApplicationListener 加載、SpringApplication類的參數(shù)配置功能, 以及針對這些操作我們能夠進行的自定義組件及配置。建議大家在學(xué)習(xí)的過程中可配合相應(yīng)的實戰(zhàn)練習(xí),獲得更好的學(xué)習(xí)效果。
本文給大家講解的內(nèi)容是ApplicationListener加載和入口類推斷、SpringApplication 的定制化配置
下篇文章給大家講解的是SpringBoot運行流程源碼分析;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號里找我,我等你哦。
