SpringBoot源碼學習1——SpringBoot自動裝配源碼解析+Spring如何處理配置類的

系列文章目錄和關於我

一丶什麼是SpringBoot自動裝配

SpringBoot通過SPI的機制,在我們程式設計師引入一些starter之後,掃描外部引用 jar 包中的META-INF/spring.factories文件,將文件中配置的類型資訊載入到 Spring 容器,實現引入starter即可開啟相關功能的操作,大大簡化了程式設計師手動配置bean,即開即用。

二丶SpringBoot自動裝配源碼解析

1.源碼解析入口

 SpringApplication.run(啟動類.class, args)

這是我們最常用的Main方法啟動SpringBoot服務的方式,其中啟動類上需要標註@SpringBootApplication註解,自動裝配,掃描主類下所有Bean的奧秘就在此

2.@SpringBootApplication註解

image-20220918003500111

@SpringBootApplication本身沒有什麼神奇的地方,重要的是註解上面標註了@SpringBootConfiguration,@EnableAutoConfiguration,和@ComponentScan註解

  • @SpringBootConfiguration平平無奇,上面標註了@Configuration表示標註的類是一個配置類

  • @EnableAutoConfiguration

    表示開啟自動配置,即我們說的SpringBoot自動裝配、

    image-20220918003812679

    • @AutoConfigurationPackage其上方標註了@Import(AutoConfigurationPackages.Registrar.class),加上@EnableAutoConfiguration上的@Import(AutoConfigurationImportSelector.class).@Import註解的作用是導入一些bean到Spring容器中,實現此功能的是ConfigurationClassPostProcessor,它是一個BeanFactoryPostProcessor會解析配置類中的@Bean,@Import,@ComponentScan等註解
  • @ComponentScan,指導Spring容器需要掃描哪些包下的類加入到Spring容器

也就是說@SpringBootApplication相當於

image-20220918011323813

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)其實最終調用的是

image-20220918120649722

在其構造方法中:

  • 根據當前項目判斷Web應用類型

    image-20220918121017057

  • 初始化ApplicationContextInitializer,和 ApplicationListener

    這部分是通過讀META-INF/spring.factories中的內容反射進行初始化,前者是用於在刷新之前初始化 Spring ConfigurableApplicationContext 的回調介面,後者是Spring監聽器,後續會進行專門的學習。

  • 獲取主類

    會new出一個RuntimeException,然後分析StackTraceElement找到方法名稱為main,然後獲取類名

springboot啟動源碼 自動配置需要關注的部分框出

image-20220918012400025

4.2 根據項目創建一個合適的ApplicationConext

image-20220918121530140

這裡便是通過web應用的類型,反射生成AnnotationConfigServletWebServerApplicationContext類型的上下文,也就是說,如果當前項目中存在Servlet,和ConfigurableWebApplicationContext那麼SpringBoot會選擇AnnotationConfigServletWebServerApplicationContext

image-20220918122547330

其中ServletWebServerApplicationContext具備啟動Serlvet伺服器(如Tomcat)並將Servlet 類型的bean或過濾器類型的 bean 都註冊到 Web 伺服器的能力

AnnotationConfigServletWebServerApplicationContext則是在ServletWebServerApplicationContext上增加了根據類路徑掃描,註冊Component到上下文的能力

4.3 刷新ApplicationContext的前置準備

prepareContext方法中,SpringBoot會把主類註冊到Spring容器中,為什麼要這麼做昵,——主類上的註解@SpringBootApplication需要ConfigurationClassPostProcessor解析,才能發揮@Import,@ComponentScan的作用,想要ConfigurationClassPostProcessor處理主類的前提是主類的BeanDefinition需要在Spring容器中

image-20220918124035091

image-20220918124301767

這裡的BeanDefinitionRegistry即是AnnotationConfigServletWebServerApplicationContext中持有的DefaultListableBeanFactory

image-20220918124524477

如果是CharSequence類型,會嘗試使用Class.forName解析成類,然後嘗試使用解析Resouce,解析的Package的方式處理。

image-20220918124811223

這裡使用AnnotatedBeanDefinitionReader註冊我們的主類,此類在spring源碼學習筆記2——基於註解生成BeanDefinition的過程解析中學習過。

簡單來說就是會將主類的資訊包裝成AnnotatedGenericBeanDefinition,其中會解析@Scope,@Lazy,@Primary@DependsOn等註解設置到AnnotatedGenericBeanDefinition中,然後調用BeanDefinitionCustomizer#customize允許我們自定義處理BeanDefinition。

4.4 刷新ApplicationContext

這裡就是調用AnnotationConfigServletWebServerApplicationContext#refresh方法,來到AbstractApplicationContextrefresh方法中,執行流程如下

image-20220904120106897

這裡我們需要注意調用BeanFactoryPostProcessor,因為這裡將調用到ConfigurationClassPostProcessor,接下來我們將分析其源碼,看看它究竟做了什麼

image-20220918135127426

4.4.1解析配置類中的相關註解

這一步發生在BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry

4.4.1.1遍歷所有的BeanDefinition進行篩選

image-20220918135512943

可以看到如果需要處理,會放入到集合中,那麼什麼樣的類才需要進一步處理昵,首先這個類的BeanDefinition需要存在於Spring容器中

  • 具備@Configuration註解,會被標記為了full模式
  • 具備@Component,@ComponentScan,@Import@ImportResource其中任何一個註解,會被標記為lite模式
  • 具備一個方法標註了@Bean註解,會被標記為lite模式

fulllite的區別後面會將

獲取候選者後會根據其@Order註解中的順序進行排序,SpringBoot項目通常這時候只有主類

4.4.1.2 使用ConfigurationClassParser解析候選者

image-20220918141356643

在這裡SpringBoot的啟動類,會被解析,

  1. 首先是進行條件註解解析,如果不符合條件那麼什麼都不做。這裡的條件註解指的是@Conditional及其複合註解@ConditionOnClass,@ConditionOnBean

  2. 進行解析

    image-20220918144647107

    這裡循環當前類和其父類調用doProcessConfigurationClass進行解析,需要注意的是:如果父類上的Condition註解不滿足,但是子類滿足,但是子類是一個配置類,父類中的@Bean等註解,還是會進行解析

    1. 如果標註了@Component及其複合註解那麼解析內部類

      ConfigurationClassParser會把當前配置類中的內部類也當作配置類解析,也就是說如果A是一個配置類候選者,內部類沒有@Component,@Configuration也會當做配置進行解析

    2. 解析@PropertySources@PropertySource

      ConfigurationClassParser會將@PropertySources指定的配置,加入到Environment

    3. 解析@ComponentScans@ComponentScan

      這一步會確認條件註解中的內容滿足,然後使用ComponentScanAnnotationParser,獲取指定的路徑,如果沒有指定任何路徑那麼使用當前配置所在的路徑,這也是為什麼SpringBoot主類上沒有指定掃描路徑,但是默認載入主類所在包下所有類。掃描包路徑下的所有類,使用指定的TypeFilter進行過濾(檢查是否具備@Component註解)且條件註解滿足才會註冊對應的BeanDefinition到容器中,這裡SpringBoot指定了AutoConfigurationExcludeFilter,其作用是排除掉掃描到的自動裝配類,因為自動裝配類由@Import(AutoConfigurationImportSelector.class)導入的AutoConfigurationImportSelector來處理

      image-20220918151130266

      掃到的類,還會當前配置類進行解析,如果是一個配置類即滿足

      • 具備@Configuration註解,會被標記為了full模式
      • 具備@Component,@ComponentScan,@Import@ImportResource其中任何一個註解,會被標記為lite模式
      • 具備一個方法標註了@Bean註解,會被標記為lite模式

      任何一個條件 那麼會再次處理,有點遞歸的意思

    4. 處理@@Import註解

      獲取類上面的@Import註解內容

      • 導入的類是ImportSelector類型

        反射實例化ImportSelector

        如果此ImportSelector實現了BeanClassLoaderAware,BeanFactoryAwareEnvironmentAware,EnvironmentAware,ResourceLoaderAware會回調對應的方法

        調用當前ImportSelectorselectImports,然後遞歸執行處理@Import註解的方法,也就是說可以導入一個具備@Import的類,如果沒有“@Import`那麼當中配置類解析

      • 導入的類是ImportBeanDefinitionRegistrar類型

        反射實例化ImportBeanDefinitionRegistrar,然後加入到importBeanDefinitionRegistrars集合中後續會回調其registerBeanDefinitions

      • 既不是ImportBeanDefinitionRegistrar也不是ImportSelector,將導入的類當做配置類處理,後續會判斷條件註解是否滿足,然後解析導入的類,並且解析其父類

      這一步便會解析到 @Import(AutoConfigurationImportSelector.class)進行自動裝配,具體操作後續講解
      
    5. 處理@ImportResource註解

      獲取註解中指定的路徑資源,和指定的BeanDefinitionReader類型,然後包裝到importedResources集合中,後續回調BeanDefinitionReader#loadBeanDefinitions(默認使用XmlBeanDefinitionReader),也就是說我們可以使用@ImportResource導入一些定義在xml中的bean

    6. 處理標註@Bean註解的方法

      會掃描標註@Bean的方法,存到beanMethods集合中,後續解析方法上的條件註解,如果滿足條件,將包裝成ConfigurationClassBeanDefinition,其中bean名稱和別名來自@Bean中name指定,並且指定其factoryMethodName,後續實例化bean的時候將反射調用標註的方法生成bean,然後解析@Lazy,@Primary,@DependsOn等註解,還會解析@Bean註解中標註的是否依賴注入候選者,初始化方法,銷毀方法,以及@Scope註解,然後註冊到BeanDefinitionRegistry中

    7. 處理介面中標註@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到底做了什麼來實現自動裝配

image-20220918161740202

首先我們可以通過spring.boot.enableautoconfiguration來設置是否開啟自動配置,那怕再配置類上面標註了@EnableAutoConfiguration也可以進行關閉。

然後會先讀取spring-autoconfigure-metadata.properties ,此文件存儲的是」待自動裝配候選類「過濾的計算規則,會根據裡面的規則逐一對候選類進行計算看是否需要被自動裝配進容器,並不是全部載入

然後是讀取META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration對應的自動配置類,如

image-20220918162740778

image-20220918163005271

  • 首先使用classLoader讀META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration對應的內容,然後進行去重

  • 然後獲取自動裝配註解標註的excludeexcludeName表示不需要進行自動裝配的類,並排除掉這些類

  • 然後獲取META-INF/spring.factoriesorg.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了(腦子:你回了。手:不,我不會)