你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

前言

沒有面試就繼續(xù)夯實(shí)自己的基礎(chǔ),前陣子的在面試過程中遇到的各種問題陸陸續(xù)續(xù)都會(huì)總結(jié)出來分享給大家,這次要說的也是面試中被問到的一個(gè)高頻的問題,我當(dāng)時(shí)其實(shí)沒答好,因?yàn)楹茉缰笆强吹絪pringboot的啟動(dòng)的一個(gè)過程的源碼的,但是時(shí)間隔得有點(diǎn)久了(兩年多沒用過springboot),所以當(dāng)時(shí)也沒答好。這次好好總結(jié)這部分知識。

SpringApplication.run()

我看網(wǎng)上好多介紹springboot自動(dòng)裝配過的文章時(shí),上來就直接說@SpringBootApplication 注解是一個(gè)復(fù)合注解,從這個(gè)注解開始介紹springboot是如何將配置項(xiàng)進(jìn)行加載的。其實(shí)我覺得難道不應(yīng)該是先啟動(dòng)了spring的容器,然后才能掃到注解,然后才能解析注解嗎?也可能是大家覺得創(chuàng)建容器刷新容器這些基礎(chǔ)操作都默認(rèn)知道的,所以就都沒說。

但我在分析springboot自動(dòng)裝配的時(shí)候,要先從 SpringApplication.run() 方法開始。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

我們進(jìn)入到 SpringApplication 這個(gè)類中看一下 run() 方法的核心實(shí)現(xiàn),差不多每一行我都加上了注釋了。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

SpringApplication.run() 方法中,我把關(guān)鍵點(diǎn)用序號標(biāo)識出來了。

  1. 第一個(gè)就是創(chuàng)建ApplicationContext容器。
  2. 第二個(gè)是刷新ApplicationContext容器。

在創(chuàng)建ApplicationContext時(shí),會(huì)根據(jù)用戶是否明確設(shè)置了 ApplicationContextClass 類型以及初始化階段的推斷結(jié)果,決定為當(dāng)前SpringBoot應(yīng)用創(chuàng)建什么類型的ApplicationContext。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

創(chuàng)建完成ApplicationContext容器后,我們接著回到 SpringApplication.run() 方法中。

下面開始初始化各種插件在異常失敗后給出的提示。

然后執(zhí)行準(zhǔn)備刷新上下文的一些操作。其實(shí) prepareContext() 方法也是非常關(guān)鍵的,它起到了一個(gè)承上啟下的作用。下面我們來看一下 prepareContext() 方法里面具體執(zhí)行了什么。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

關(guān)鍵的地方我也標(biāo)注出來了,主要就是 getAllSoures() 方法,這個(gè)方法中,獲取到的一個(gè)source就是啟動(dòng)類DemoApplication。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

這樣就通過獲取這個(gè)啟動(dòng)類就可以在后load()方法中取加載這個(gè)啟動(dòng)類到容器中。

然后,后面再通過 listeners.contextLoaded(context) ;

將所有監(jiān)聽器加載到ApplicationContext容器中。

最后就是我們上面說的核心的第二部刷新ApplicationContext容器操作,如果沒有這一步操作上面的內(nèi)容也都白做的,通過 SpringApplication的refreshContext(context) 方法完成最后一道工序?qū)?dòng)類上的注解配置,刷新到當(dāng)前運(yùn)行的容器環(huán)境中。

啟動(dòng)類上的注解

上面我們說到在SpringApplication的 run() 方法中,通過調(diào)用自己的 prepareContext() 方法,在 prepareContext() 方法中又調(diào)用 getAllSources() 方法,然后去獲取啟動(dòng)類,然后通過SpringApplication的 load() 方法,去加載啟動(dòng)類,然后在刷新容器的時(shí)候就會(huì)去將啟動(dòng)類在容器中進(jìn)行實(shí)例化。

在刷新ApplicationContext容器時(shí),就開始解析啟動(dòng)類上的注解了。

啟動(dòng)類 DemoApplication 就只有一個(gè)注解 @SpringBootApplication ,那么下面來看一下這個(gè)注解:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

可以看到這個(gè)注解是一個(gè)復(fù)合注解,有三個(gè)關(guān)鍵注解需要說明一下。

@SpringBootConfiguration

@SpringBootConfiguration 這個(gè)注解說明再點(diǎn)進(jìn)去查看詳情發(fā)現(xiàn)就是一個(gè)@Configuration 注解,這說明啟動(dòng)類就是一個(gè)配置類。支持Spring以JavaConfig的形式啟動(dòng)。

@ComponentScan

這個(gè)注解,從字面的意思上也能看出來,就是組件掃描的意思,即默認(rèn)掃描當(dāng)前package以及其子包下面的spring的注解,例如: @Controller 、 @Service 、 @Component 等等注解。

@EnableAutoConfiguration

@EnableAutoConfiguration 這個(gè)注解也是一個(gè)復(fù)合注解:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

這個(gè)注解是比較核心的一個(gè)注解,springboot的主要自動(dòng)配置原理基本上都來自@EnableAutoConfiguration這個(gè)注解的配置,那么我們通過看這個(gè)注解的源碼可以發(fā)現(xiàn)有兩個(gè)注解比較重要的。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage 這個(gè)注解字面的意思是 自動(dòng)配置包 ,那么我們點(diǎn)進(jìn)去看看里面是什么樣的。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

還是一個(gè)復(fù)合注解,但是最終依賴的確實(shí) @Import 這個(gè)注解,這個(gè)注解后面我們會(huì)介紹,現(xiàn)在先明白它就是給Spring容器引入組件的功能的一個(gè)注解。

那么我們接著來看看 AutoConfigurationPackages.Registrar.class 這個(gè)類里面的代碼。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧
你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

這兩張圖就是這個(gè) AutoConfigurationPackages.Registrar 這個(gè)類的關(guān)鍵部分,說實(shí)話,我是沒看出來什么東西。但是網(wǎng)上搜到的是這個(gè)register()方法的作用是,用來自動(dòng)注冊一些組件中的配置,例如JPA的 @Entity 這個(gè)注解,這里就是會(huì)開啟自動(dòng)掃描這類注解的功能。

@Import(AutoConfigurationImportSelector.class)

我們接著回來看 @EnableAutoConfiguration 下的@Import(AutoConfigurationImportSelector.class) 這個(gè)注解的功能。進(jìn)入到AutoConfigurationImportSelector 這個(gè)類里面后源碼如下:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

然后我們進(jìn)入 getAutoConfigurationEntry() 方法來看看:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

我們繼續(xù)進(jìn)入 getCandidateConfigurations() 方法:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

看來最核心的方法是 SpringFactroiesLoader.loadFactoryNames() 方法了,我們再進(jìn)入看看:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

包的好深,居然還有一層,那么繼續(xù)進(jìn)入 loadSpringFactories() 方法。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

終于到最后一層了,算是“撥開云霧見天日,守得云開見月明”,下面就來梳理一下loadSpringFactories()方法。

首先 FACTORIES_RESOURCE_LOCATION 這個(gè)常量的值是:

"META-INF/spring.factories"

/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files.
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

所以第一個(gè)端核心代碼的意思是:

啟動(dòng)的時(shí)候會(huì)掃描所有jar包下 META-INF/spring.factories 這個(gè)文件。第二段代碼的意思是將這些掃描到的文件轉(zhuǎn)成Properties對象,后面兩個(gè)核心代碼的意思就是說將加載到的Properties對象放入到緩存中。

然后 getCandidateConfigurations() 方法,是只獲取了key是EnableAutoConfiguration.class 的配置。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

我們看到 getCandidateConfigurations() 方法,通過SpringFactoriesLoader.loadFactoryNames() 獲取到了118個(gè)配置。

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

那么我們來看一個(gè) spring.factories 文件中的內(nèi)容是什么樣子的呢?

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

原來是這種形式的,看來這和上一篇文章中講解的Java中的SPI機(jī)制加載接口實(shí)現(xiàn)很像啊,其實(shí)通過查閱資料發(fā)現(xiàn),這就是一種自定義SPI的實(shí)現(xiàn)方式的功能。

那么我們以第一個(gè)配置類:

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration 來看一下,這些類都是如果實(shí)現(xiàn)的。

打開org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration 的源碼:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

我們看到這個(gè)類有三個(gè)注解 @Configuration 、 @AutoConfigureAfter 、@ConditionalOnProperty 、因?yàn)橛?@Configuration 注解所以它也是一個(gè)配置類,然后第二注解中的參數(shù)類 JmxAutoConfiguration.class 進(jìn)入之后是這樣的:

你來說一下springboot的啟動(dòng)時(shí)的一個(gè)自動(dòng)裝配過程吧

也是存在 @ConditionalOnProperty 注解的。那看來關(guān)鍵點(diǎn)就是 @ConditionalOnProperty這個(gè)注解了。

這個(gè)注解其實(shí)是一個(gè)條件判斷注解,這個(gè)條件注解后面的參數(shù)的意思是當(dāng)存在系統(tǒng)屬性前綴為spring.application.admin ,并且屬性名稱為 enabled ,并且值為 true 時(shí),才加載當(dāng)前這個(gè)Bean并進(jìn)行實(shí)例化。

這種spring4.0后面出現(xiàn)的的條件注解,可以極大的增加了框架的靈活性和擴(kuò)展性,可以保證很多組件可以通過后期配置,而且閱讀源碼的人,通過這些注解就能明白在什么情況下才會(huì)實(shí)例化當(dāng)前Bean。

后面還有不少這種條件注解呢:

@ConditionalOnBean:當(dāng)容器里有指定Bean的條件下

@ConditionalOnClass:當(dāng)類路徑下有指定的類的條件下

@ConditionalOnExpression:基于SpEL表達(dá)式為true的時(shí)候作為判斷條件才去實(shí)例化

@ConditionalOnJava:基于JVM版本作為判斷條件

@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置

@ConditionalOnMissingBean:當(dāng)容器里沒有指定Bean的情況下

@ConditionalOnMissingClass:當(dāng)容器里沒有指定類的情況下

@ConditionalOnWebApplication:當(dāng)前項(xiàng)目時(shí)Web項(xiàng)目的條件下

@ConditionalOnNotWebApplication:當(dāng)前項(xiàng)目不是Web項(xiàng)目的條件下

@ConditionalOnProperty:指定的屬性是否有指定的值

@ConditionalOnResource:類路徑是否有指定的值

@ConditionalOnOnSingleCandidate:當(dāng)指定Bean在容器中只有一個(gè),或者有多個(gè)但是指定首選的Bean

這些注解其實(shí)都是通過@Conditional注解擴(kuò)展而來的,只是使用了不同的組合條件來判斷是否需要加載和初始化當(dāng)前Bean。

總結(jié)

好了,最后總結(jié)一下,當(dāng)面試官問springboot的自動(dòng)裝配原理的時(shí)候,不能這么長篇大論的說吧,畢竟這么多內(nèi)容也記不住啊。

所以總結(jié):

springboot啟動(dòng)時(shí),是依靠啟動(dòng)類的main方法來進(jìn)行啟動(dòng)的,而main方法中執(zhí)行的是SpringApplication.run() 方法,而 SpringApplication.run() 方法中會(huì)創(chuàng)建spring的容器,并且刷新容器。而在刷新容器的時(shí)候就會(huì)去解析啟動(dòng)類,然后就會(huì)去解析啟動(dòng)類上的@SpringBootApplication 注解,而這個(gè)注解是個(gè)復(fù)合注解,這個(gè)注解中有一個(gè)@EnableAutoConfiguration 注解,這個(gè)注解就是開啟自動(dòng)配置,這個(gè)注解中又有 @Import注解引入了一個(gè) AutoConfigurationImportSelector 這個(gè)類,這個(gè)類會(huì)進(jìn)過一些核心方法,然后去掃描我們所有jar包下的 META-INF 下的 spring.factories 文件,而從這個(gè)配置文件中取找key為 EnableAutoConfiguration 類的全路徑的值下面的所有配置都加載,這些配置里面都是有條件注解的,然后這些條件注解會(huì)根據(jù)你當(dāng)前的項(xiàng)目依賴的jar包以及是否配置了符合這些條件注解的配置來進(jìn)行裝載的。

這就是springboot自動(dòng)配置的過程。

其實(shí)上面這些內(nèi)容還是有點(diǎn)多,而且還有好多注解的單詞也不好記,那換成大白話,再精煉一下:

SpringBoot在啟動(dòng)的時(shí)候會(huì)調(diào)用run()方法,run()方法會(huì)刷新容器,刷新容器的時(shí)候,會(huì)掃描classpath下面的的包中META-INF/spring.factories文件,在這個(gè)文件中記錄了好多的自動(dòng)配置類,在刷新容器的時(shí)候會(huì)將這些自動(dòng)配置類加載到容器中,然后在根據(jù)這些配置類中的條件注解,來判斷是否將這些配置類在容器中進(jìn)行實(shí)例化,這些條件主要是判斷項(xiàng)目是否有相關(guān)jar包或是否引入了相關(guān)的bean。這樣springboot就幫助我們完成了自動(dòng)裝配。

作者:紀(jì)莫
來源:https://www.tuicool.com/articles/77FFZnJ

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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