SpringBoot通過SPI的機制,在我們程式設計師引入一些starter之後,掃描外部參照 jar 包中的META-INF/spring.factories
檔案,將檔案中設定的型別資訊載入到 Spring 容器,實現引入starter即可開啟相關功能的操作,大大簡化了程式設計師手動設定bean,即開即用。
SpringApplication.run(啟動類.class, args)
這是我們最常用的Main方法啟動SpringBoot服務的方式,其中啟動類上需要標註@SpringBootApplication
註解,自動裝配,掃描主類下所有Bean的奧祕就在此
@SpringBootApplication
本身沒有什麼神奇的地方,重要的是註解上面標註了@SpringBootConfiguration
,@EnableAutoConfiguration
,和@ComponentScan
註解
@SpringBootConfiguration
平平無奇,上面標註了@Configuration
表示標註的類是一個設定類
@EnableAutoConfiguration
表示開啟自動設定,即我們說的SpringBoot自動裝配、
@AutoConfigurationPackage
其上方標註了@Import(AutoConfigurationPackages.Registrar.class)
,加上@EnableAutoConfiguration
上的@Import(AutoConfigurationImportSelector.class)
.@Import
註解的作用是匯入一些bean到Spring容器中,實現此功能的是ConfigurationClassPostProcessor
,它是一個BeanFactoryPostProcessor
會解析設定類中的@Bean,@Import,@ComponentScan等註解@ComponentScan
,指導Spring容器需要掃描哪些包下的類加入到Spring容器
也就是說@SpringBootApplication
相當於
SpringBoot並不是Spring的替代品,而是利用Spring加上約定大於設定
的思想,方便程式設計師開發的框架。所以其底層還是Spring那一套(Spring原始碼相關部落格:Spring原始碼學習筆記12——總結篇,IOC,Bean的生命週期,三大擴充套件點)
本篇著重研究SpringBoot的自動裝配原理,所以一些無關的程式碼不會進行詳細探究,後續會單獨學習整理。
既然SpringBoot是基於Spring的,那麼必然是無法脫離ApplicationContext的,接下來我們以SpringApplication#run
為入口看看,SpringApplication是如何初始化一個ApplicationContext的
我們在啟動類的main方法中SpringApplication.run(啟動類.class, args)
其實最終呼叫的是
在其構造方法中:
根據當前專案判斷Web應用型別
初始化ApplicationContextInitializer
,和 ApplicationListener
這部分是通過讀META-INF/spring.factories
中的內容反射進行初始化,前者是用於在重新整理之前初始化 Spring ConfigurableApplicationContext 的回撥介面,後者是Spring監聽器,後續會進行專門的學習。
獲取主類
會new出一個RuntimeException
,然後分析StackTraceElement
找到方法名稱為main
,然後獲取類名
springboot啟動原始碼 自動設定需要關注的部分框出
這裡便是通過web應用的型別,反射生成AnnotationConfigServletWebServerApplicationContext
型別的上下文,也就是說,如果當前專案中存在Servlet
,和ConfigurableWebApplicationContext
那麼SpringBoot會選擇AnnotationConfigServletWebServerApplicationContext
其中ServletWebServerApplicationContext
具備啟動Serlvet伺服器(如Tomcat)並將Servlet 型別的bean或過濾器型別的 bean 都註冊到 Web 伺服器的能力
AnnotationConfigServletWebServerApplicationContext
則是在ServletWebServerApplicationContext
上增加了根據類路徑掃描,註冊Component到上下文的能力
在prepareContext
方法中,SpringBoot會把主類註冊到Spring容器中,為什麼要這麼做暱,——主類上的註解@SpringBootApplication
需要ConfigurationClassPostProcessor
解析,才能發揮@Import,@ComponentScan的作用,想要ConfigurationClassPostProcessor
處理主類的前提是主類的BeanDefinition需要在Spring容器中
這裡的BeanDefinitionRegistry即是AnnotationConfigServletWebServerApplicationContext
中持有的DefaultListableBeanFactory
如果是CharSequence
型別,會嘗試使用Class.forName
解析成類,然後嘗試使用解析Resouce,解析的Package的方式處理。
這裡使用AnnotatedBeanDefinitionReader
註冊我們的主類,此類在spring原始碼學習筆記2——基於註解生成BeanDefinition的過程解析中學習過。
簡單來說就是會將主類的資訊包裝成AnnotatedGenericBeanDefinition
,其中會解析@Scope
,@Lazy
,@Primary
,@DependsOn
等註解設定到AnnotatedGenericBeanDefinition
中,然後呼叫BeanDefinitionCustomizer#customize
允許我們自定義處理BeanDefinition。
這裡就是呼叫AnnotationConfigServletWebServerApplicationContext#refresh
方法,來到AbstractApplicationContext
的refresh
方法中,執行流程如下
這裡我們需要注意呼叫BeanFactoryPostProcessor,因為這裡將呼叫到ConfigurationClassPostProcessor
,接下來我們將分析其原始碼,看看它究竟做了什麼
這一步發生在BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
中
可以看到如果需要處理,會放入到集合中,那麼什麼樣的類才需要進一步處理暱,首先這個類的BeanDefinition需要存在於Spring容器中
@Configuration
註解,會被標記為了full
模式@Component
,@ComponentScan
,@Import
,@ImportResource
其中任何一個註解,會被標記為lite
模式@Bean
註解,會被標記為lite
模式full
和lite
的區別後面會將
獲取候選者後會根據其@Order
註解中的順序進行排序,SpringBoot專案通常這時候只有主類
ConfigurationClassParser
解析候選者在這裡SpringBoot的啟動類,會被解析,
首先是進行條件註解解析,如果不符合條件那麼什麼都不做。這裡的條件註解指的是@Conditional
及其複合註解@ConditionOnClass
,@ConditionOnBean
等
進行解析
這裡迴圈當前類和其父類別呼叫doProcessConfigurationClass
進行解析,需要注意的是:如果父類別上的Condition註解不滿足,但是子類滿足,但是子類是一個設定類,父類別中的@Bean等註解,還是會進行解析
如果標註了@Component
及其複合註解那麼解析內部類
ConfigurationClassParser
會把當前設定類中的內部類也當作設定類解析,也就是說如果A是一個設定類候選者,內部類沒有@Component,@Configuration也會當做設定進行解析
解析@PropertySources
和 @PropertySource
ConfigurationClassParser
會將@PropertySources
指定的設定,加入到Environment
中
解析@ComponentScans
和 @ComponentScan
這一步會確認條件註解中的內容滿足,然後使用ComponentScanAnnotationParser
,獲取指定的路徑,如果沒有指定任何路徑那麼使用當前設定所在的路徑,這也是為什麼SpringBoot主類上沒有指定掃描路徑,但是預設載入主類所在包下所有類。掃描包路徑下的所有類,使用指定的TypeFilter
進行過濾(檢查是否具備@Component註解)且條件註解滿足才會註冊對應的BeanDefinition到容器中,這裡SpringBoot指定了AutoConfigurationExcludeFilter
,其作用是排除掉掃描到的自動裝配類,因為自動裝配類由@Import(AutoConfigurationImportSelector.class)
匯入的AutoConfigurationImportSelector
來處理
掃到的類,還會當前設定類進行解析,如果是一個設定類即滿足
@Configuration
註解,會被標記為了full
模式@Component
,@ComponentScan
,@Import
,@ImportResource
其中任何一個註解,會被標記為lite
模式@Bean
註解,會被標記為lite
模式任何一個條件 那麼會再次處理,有點遞迴的意思
處理@@Import
註解
獲取類上面的@Import
註解內容
匯入的類是ImportSelector
型別
反射範例化ImportSelector
如果此ImportSelector
實現了BeanClassLoaderAware
,BeanFactoryAware
,EnvironmentAware
,EnvironmentAware
,ResourceLoaderAware
會回撥對應的方法
呼叫當前ImportSelector
的selectImports
,然後遞迴執行處理@Import
註解的方法,也就是說可以匯入一個具備@Import
的類,如果沒有``@Import`那麼當中設定類解析
匯入的類是ImportBeanDefinitionRegistrar
型別
反射範例化ImportBeanDefinitionRegistrar
,然後加入到importBeanDefinitionRegistrars
集合中後續會回撥其registerBeanDefinitions
既不是ImportBeanDefinitionRegistrar
也不是ImportSelector
,將匯入的類當做設定類處理,後續會判斷條件註解是否滿足,然後解析匯入的類,並且解析其父類別
這一步便會解析到 @Import(AutoConfigurationImportSelector.class)進行自動裝配,具體操作後續講解
處理@ImportResource
註解
獲取註解中指定的路徑資源,和指定的BeanDefinitionReader
型別,然後包裝到importedResources
集合中,後續回撥BeanDefinitionReader#loadBeanDefinitions
(預設使用XmlBeanDefinitionReader
),也就是說我們可以使用@ImportResource
匯入一些定義在xml中的bean
處理標註@Bean
註解的方法
會掃描標註@Bean
的方法,存到beanMethods
集合中,後續解析方法上的條件註解,如果滿足條件,將包裝成ConfigurationClassBeanDefinition
,其中bean名稱和別名來自@Bean中name指定,並且指定其factoryMethodName
,後續範例化bean的時候將反射呼叫標註的方法生成bean,然後解析@Lazy
,@Primary
,@DependsOn
等註解,還會解析@Bean註解中標註的是否依賴注入候選者,初始化方法,銷燬方法,以及@Scope
註解,然後註冊到BeanDefinitionRegistry中
處理介面中標註@Bean的預設方法
獲取當前類實現的全部的介面,且非抽象的方法,然後進行6.處理標註
@Bean註解的方法
在ConfigurationClassPostProcessor#postProcessBeanFactory
方法中,會對full
模式的設定進行增強,full模式指標註@Configuration註解的類,呼叫其enhanceConfigurationClasses
方法,攔截@Bean
方法,以確保正確處理@Bean
語意。Spring將使用CGLIB對原設定進行增強,獲取增強後的類,替換呼叫原BeanDefinition記錄的類,後續將使用此加強類,這也做的目的在於,呼叫設定類標註了@Bean方法的時候,不會真正呼叫其中的邏輯,而是直接取BeanFactory#getBean
中取,保證@Bean標註的方法,產生bean的生命週期完整
上面關於ConfigurationClassPostProcessor
類的原始碼解析,我們明白了Spring是如何解析一個設定類的,其中和SpringBoot自動裝配關係最密切的是對@Import
註解,SpringBoot啟動上標註的@SpringBootApplication
包含了@Import(AutoConfigurationImportSelector.class)
,下面我們將解析AutoConfigurationImportSelector
到底做了什麼來實現自動裝配
首先我們可以通過spring.boot.enableautoconfiguration
來設定是否開啟自動設定,那怕再設定類上面標註了@EnableAutoConfiguration
也可以進行關閉。
然後會先讀取spring-autoconfigure-metadata.properties ,此檔案儲存的是」待自動裝配候選類「過濾的計算規則,會根據裡面的規則逐一對候選類進行計算看是否需要被自動裝配進容器,並不是全部載入
然後是讀取META-INF/spring.factories
中org.springframework.boot.autoconfigure.EnableAutoConfiguration
對應的自動設定類,如
首先使用classLoader讀META-INF/spring.factories
中org.springframework.boot.autoconfigure.EnableAutoConfiguration
對應的內容,然後進行去重
然後獲取自動裝配註解標註的exclude
和excludeName
表示不需要進行自動裝配的類,並排除掉這些類
然後獲取META-INF/spring.factories
中org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
對應的內容,範例化成AutoConfigurationImportFilter
呼叫其match
方法,判斷這些自動裝配類是否需要被過濾掉,這是springboot留給我們的一個擴充套件點,如果需要讀取快取中的內容進行對自動設定類的過濾,我們可以自己實現一個AutoConfigurationImportFilter
放在META-INF/spring.factories
中,如org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=com.a.xx
即可進行自定義過濾
緊接著會傳送一個AutoConfigurationImportEvent
事件
關於SpringBoot的事件會在下一篇中講解
最後會把需要自動裝配的類全限定類名返回,接著就到了ConfigurationClassPostProcessor
中,它會繼續使用ConfigurationClassParser
將這些自動設定類進一步解析
有了這些知識,我們可以寫一個自己的starter了(腦子:你回了。手:不,我不會)