SpringBoot源碼學習1——SpringBoot自動裝配源碼解析+Spring如何處理配置類的
- 2022 年 9 月 18 日
- 筆記
- springboot
一丶什麼是SpringBoot自動裝配
SpringBoot通過SPI的機制,在我們程式設計師引入一些starter之後,掃描外部引用 jar 包中的META-INF/spring.factories
文件,將文件中配置的類型資訊載入到 Spring 容器,實現引入starter即可開啟相關功能的操作,大大簡化了程式設計師手動配置bean,即開即用。
二丶SpringBoot自動裝配源碼解析
1.源碼解析入口
SpringApplication.run(啟動類.class, args)
這是我們最常用的Main方法啟動SpringBoot服務的方式,其中啟動類上需要標註@SpringBootApplication
註解,自動裝配,掃描主類下所有Bean的奧秘就在此
2.@SpringBootApplication註解
@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
相當於
3.自動配置源碼學習前言
SpringBoot並不是Spring的替代品,而是利用Spring加上約定大於配置
的思想,方便程式設計師開發的框架。所以其底層還是Spring那一套(Spring源碼相關部落格:Spring源碼學習筆記12——總結篇,IOC,Bean的生命周期,三大擴展點)
本篇著重研究SpringBoot的自動裝配原理,所以一些無關的程式碼不會進行詳細探究,後續會單獨學習整理。
4.SpringApplication#run是如何初始化ApplicationContext的
既然SpringBoot是基於Spring的,那麼必然是無法脫離ApplicationContext的,接下來我們以SpringApplication#run
為入口看看,SpringApplication是如何初始化一個ApplicationContext的
4.1 SpringApplication的初始化
我們在啟動類的main方法中SpringApplication.run(啟動類.class, args)
其實最終調用的是
在其構造方法中:
-
根據當前項目判斷Web應用類型
-
初始化
ApplicationContextInitializer
,和ApplicationListener
這部分是通過讀
META-INF/spring.factories
中的內容反射進行初始化,前者是用於在刷新之前初始化 Spring ConfigurableApplicationContext 的回調介面,後者是Spring監聽器,後續會進行專門的學習。 -
獲取主類
會new出一個
RuntimeException
,然後分析StackTraceElement
找到方法名稱為main
,然後獲取類名
springboot啟動源碼 自動配置需要關注的部分框出
4.2 根據項目創建一個合適的ApplicationConext
這裡便是通過web應用的類型,反射生成AnnotationConfigServletWebServerApplicationContext
類型的上下文,也就是說,如果當前項目中存在Servlet
,和ConfigurableWebApplicationContext
那麼SpringBoot會選擇AnnotationConfigServletWebServerApplicationContext
其中ServletWebServerApplicationContext
具備啟動Serlvet伺服器(如Tomcat)並將Servlet 類型的bean或過濾器類型的 bean 都註冊到 Web 伺服器的能力
AnnotationConfigServletWebServerApplicationContext
則是在ServletWebServerApplicationContext
上增加了根據類路徑掃描,註冊Component到上下文的能力
4.3 刷新ApplicationContext的前置準備
在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。
4.4 刷新ApplicationContext
這裡就是調用AnnotationConfigServletWebServerApplicationContext#refresh
方法,來到AbstractApplicationContext
的refresh
方法中,執行流程如下
這裡我們需要注意調用BeanFactoryPostProcessor,因為這裡將調用到ConfigurationClassPostProcessor
,接下來我們將分析其源碼,看看它究竟做了什麼
4.4.1解析配置類中的相關註解
這一步發生在BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
中
4.4.1.1遍歷所有的BeanDefinition進行篩選
可以看到如果需要處理,會放入到集合中,那麼什麼樣的類才需要進一步處理昵,首先這個類的BeanDefinition需要存在於Spring容器中
- 具備
@Configuration
註解,會被標記為了full
模式 - 具備
@Component
,@ComponentScan
,@Import
,@ImportResource
其中任何一個註解,會被標記為lite
模式 - 具備一個方法標註了
@Bean
註解,會被標記為lite
模式
full
和lite
的區別後面會將
獲取候選者後會根據其@Order
註解中的順序進行排序,SpringBoot項目通常這時候只有主類
4.4.1.2 使用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註解的方法
-
4.4.2增強配置類
在ConfigurationClassPostProcessor#postProcessBeanFactory
方法中,會對full
模式的配置進行增強,full模式指標註@Configuration註解的類,調用其enhanceConfigurationClasses
方法,攔截@Bean
方法,以確保正確處理@Bean
語義。Spring將使用CGLIB對原配置進行增強,獲取增強後的類,替換調用原BeanDefinition記錄的類,後續將使用此加強類,這也做的目的在於,調用配置類標註了@Bean方法的時候,不會真正調用其中的邏輯,而是直接取BeanFactory#getBean
中取,保證@Bean標註的方法,產生bean的生命周期完整
4.5 自動裝配源碼解析
上面關於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了(腦子:你回了。手:不,我不會)