繼續總結吧,沒有面試就繼續夯實本身的基礎,前陣子的在面試過程當中遇到的各類問題陸陸續續都會總結出來分享給你們,此次要說的也是面試中被問到的一個高頻的問題,我當時其實沒答好,由於很早以前是看到springboot的啓動的一個過程的源碼的,可是時間隔得有點久了(兩年多沒用過springboot),因此當時也沒答好。此次好好總結這部分知識。java
我看網上好多介紹springboot自動裝配過的文章時,上來就直接說@SpringBootApplication
註解是一個複合註解,從這個註解開始介紹springboot是如何將配置項進行加載的。其實我以爲難道不該該是先啓動了spring的容器,而後才能掃到註解,而後才能解析註解嗎?也多是你們以爲建立容器刷新容器這些基礎操做都默認知道的,因此就都沒說。
但我在分析springboot自動裝配的時候,要先從SpringApplication.run()
方法開始。
咱們進入到SpringApplication
這個類中看一下run()
方法的核心實現,差很少每一行我都加上了註釋了。
SpringApplication.run()
方法中,我把關鍵點用序號標識出來了。面試
在建立ApplicationContext時,會根據用戶是否明確設置了ApplicationContextClass
類型以及初始化階段的推斷結果,決定爲當前SpringBoot應用建立什麼類型的ApplicationContext。
建立完成ApplicationContext容器後,咱們接着回到SpringApplication.run()
方法中。
下面開始初始化各類插件在異常失敗後給出的提示。
而後執行準備刷新上下文的一些操做。其實prepareContext()
方法也是很是關鍵的,它起到了一個承上啓下的做用。下面咱們來看一下prepareContext()
方法裏面具體執行了什麼。
關鍵的地方我也標註出來了,主要就是getAllSoures()
方法,這個方法中,獲取到的一個source就是啓動類DemoApplication。
這樣就經過獲取這個啓動類就能夠在後load()方法中取加載這個啓動類到容器中。spring
而後,後面再經過listeners.contextLoaded(context)
;
將全部監聽器加載到ApplicationContext容器中。緩存
最後就是咱們上面說的核心的第二部刷新ApplicationContext容器操做,若是沒有這一步操做上面的內容也都白作的,經過SpringApplication的refreshContext(context)
方法完成最後一道工序將啓動類上的註解配置,刷新到當前運行的容器環境中。springboot
上面咱們說到在SpringApplication的run()
方法中,經過調用本身的prepareContext()
方法,在prepareContext()
方法中又調用getAllSources()
方法,而後去獲取啓動類,而後經過SpringApplication的load()
方法,去加載啓動類,而後在刷新容器的時候就會去將啓動類在容器中進行實例化。app
在刷新ApplicationContext容器時,就開始解析啓動類上的註解了。框架
啓動類DemoApplication
就只有一個註解@SpringBootApplication
,那麼下面來看一下這個註解:
能夠看到這個註解是一個複合註解,有三個關鍵註解須要說明一下。spa
@SpringBootConfiguration
這個註解說明再點進去查看詳情發現就是一個@Configuration
註解,這說明啓動類就是一個配置類。支持Spring以JavaConfig的形式啓動。插件
這個註解,從字面的意思上也能看出來,就是組件掃描的意思,即默認掃描當前package以及其子包下面的spring的註解,例如:@Controller
、@Service
、@Component
等等註解。3d
@EnableAutoConfiguration
這個註解也是一個複合註解:
這個註解是比較核心的一個註解,springboot的主要自動配置原理基本上都來自@EnableAutoConfiguration這個註解的配置,那麼咱們經過看這個註解的源碼能夠發現有兩個註解比較重要的。
@AutoConfigurationPackage
,自動配置包。@Import(AutoConfigurationImportSelector.class)
,自動引入組件。@AutoConfigurationPackage
這個註解字面的意思是自動配置包,那麼咱們點進去看看裏面是什麼樣的。
仍是一個複合註解,可是最終依賴的確實@Import
這個註解,這個註解後面咱們會介紹,如今先明白它就是給Spring容器引入組件的功能的一個註解。
那麼咱們接着來看看AutoConfigurationPackages.Registrar.class
這個類裏面的代碼。
這兩張圖就是這個AutoConfigurationPackages.Registrar
這個類的關鍵部分,說實話,我是沒看出來什麼東西。可是網上搜到的是這個register()方法的做用是,用來自動註冊一些組件中的配置,例如JPA的@Entity
這個註解,這裏就是會開啓自動掃描這類註解的功能。
咱們接着回來看@EnableAutoConfiguration
下的@Import(AutoConfigurationImportSelector.class)
這個註解的功能。進入到AutoConfigurationImportSelector
這個類裏面後源碼以下:
而後咱們進入getAutoConfigurationEntry()
方法來看看:
咱們繼續進入getCandidateConfigurations()
方法:
看來最核心的方法是SpringFactroiesLoader.loadFactoryNames()
方法了,咱們再進入看看:
包的好深,竟然還有一層,那麼繼續進入loadSpringFactories()
方法。
終於到最後一層了,算是「撥開雲霧見天日,守得雲開見月明」,下面就來梳理一下loadSpringFactories()方法。
首先FACTORIES_RESOURCE_LOCATION
這個常量的值是:
"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";
因此第一個端核心代碼的意思是:
啓動的時候會掃描全部jar包下META-INF/spring.factories
這個文件。第二段代碼的意思是將這些掃描到的文件轉成Properties對象,後面兩個核心代碼的意思就是說將加載到的Properties對象放入到緩存中。
而後getCandidateConfigurations()
方法,是隻獲取了key是EnableAutoConfiguration.class
的配置。
咱們看到getCandidateConfigurations()
方法,經過SpringFactoriesLoader.loadFactoryNames()
獲取到了118個配置。
那麼咱們來看一個spring.factories
文件中的內容是什麼樣子的呢?
原來是這種形式的,看來這和上一篇文章中講解的Java中的SPI機制加載接口實現很像啊,其實經過查閱資料發現,這就是一種自定義SPI的實現方式的功能。
那麼咱們以第一個配置類:
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
來看一下,這些類都是若是實現的。
打開org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
的源碼:
咱們看到這個類有三個註解@Configuration
、@AutoConfigureAfter
、@ConditionalOnProperty
、由於有@Configuration
註解因此它也是一個配置類,而後第二註解中的參數類JmxAutoConfiguration.class
進入以後是這樣的:
也是存在@ConditionalOnProperty
註解的。那看來關鍵點就是@ConditionalOnProperty
這個註解了。
這個註解實際上是一個條件判斷註解,這個條件註解後面的參數的意思是當存在系統屬性前綴爲spring.application.admin
,而且屬性名稱爲enabled
,而且值爲true
時,才加載當前這個Bean並進行實例化。
這種spring4.0後面出現的的條件註解,能夠極大的增長了框架的靈活性和擴展性,能夠保證不少組件能夠經過後期配置,並且閱讀源碼的人,經過這些註解就能明白在什麼狀況下才會實例化當前Bean。
後面還有很多這種條件註解呢:
@ConditionalOnBean:當容器裏有指定Bean的條件下
@ConditionalOnClass:當類路徑下有指定的類的條件下
@ConditionalOnExpression:基於SpEL表達式爲true的時候做爲判斷條件纔去實例化
@ConditionalOnJava:基於JVM版本做爲判斷條件
@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置
@ConditionalOnMissingBean:當容器裏沒有指定Bean的狀況下
@ConditionalOnMissingClass:當容器裏沒有指定類的狀況下
@ConditionalOnWebApplication:當前項目時Web項目的條件下
@ConditionalOnNotWebApplication:當前項目不是Web項目的條件下
@ConditionalOnProperty:指定的屬性是否有指定的值
@ConditionalOnResource:類路徑是否有指定的值
@ConditionalOnOnSingleCandidate:當指定Bean在容器中只有一個,或者有多個可是指定首選的Bean
這些註解其實都是經過@Conditional註解擴展而來的,只是使用了不一樣的組合條件來判斷是否須要加載和初始化當前Bean。
好了,最後總結一下,當面試官問springboot的自動裝配原理的時候,不能這麼長篇大論的說吧,畢竟這麼多內容也記不住啊。
因此總結:
springboot啓動時,是依靠啓動類的main方法來進行啓動的,而main方法中執行的是SpringApplication.run()
方法,而SpringApplication.run()
方法中會建立spring的容器,而且刷新容器。而在刷新容器的時候就會去解析啓動類,而後就會去解析啓動類上的@SpringBootApplication
註解,而這個註解是個複合註解,這個註解中有一個@EnableAutoConfiguration
註解,這個註解就是開啓自動配置,這個註解中又有@Import
註解引入了一個AutoConfigurationImportSelector
這個類,這個類會進過一些核心方法,而後去掃描咱們全部jar包下的META-INF
下的spring.factories
文件,而從這個配置文件中取找key爲EnableAutoConfiguration
類的全路徑的值下面的全部配置都加載,這些配置裏面都是有條件註解的,而後這些條件註解會根據你當前的項目依賴的jar包以及是否配置了符合這些條件註解的配置來進行裝載的。
這就是springboot自動配置的過程。
SpringBoot在啓動的時候會調用run()方法,run()方法會刷新容器,刷新容器的時候,會掃描classpath下面的的包中META-INF/spring.factories文件,在這個文件中記錄了好多的自動配置類,在刷新容器的時候會將這些自動配置類加載到容器中,而後在根據這些配置類中的條件註解,來判斷是否將這些配置類在容器中進行實例化,這些條件主要是判斷項目是否有相關jar包或是否引入了相關的bean。這樣springboot就幫助咱們完成了自動裝配。