SpringBoot原始碼學習1——SpringBoot自動裝配原始碼解析+Spring如何處理設定類的

2022-09-18 18:00:30

系列文章目錄和關於我

一丶什麼是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方法,來到AbstractApplicationContextrefresh方法中,執行流程如下

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

4.4.1解析設定類中的相關注解

這一步發生在BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry

4.4.1.1遍歷所有的BeanDefinition進行篩選

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

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

fulllite的區別後面會將

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

4.4.1.2 使用ConfigurationClassParser解析候選者

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

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

  2. 進行解析

    這裡迴圈當前類和其父類別呼叫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來處理

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

      • 具備@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到底做了什麼來實現自動裝配

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

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

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

  • 首先使用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了(腦子:你回了。手:不,我不會)