我們使用Springboot進行開發的時候發現真的很方便,我們只需要很少的配置、少量的注解以及引入一些starter就可以完成一個簡單項目的開發。使我們受益的就是Springboot的自動配置功能,下面我們來探索Springboot的自動配置原理。(中間的一些細節的地方不做過多介紹,影響閱讀體驗,主要解析核心脈絡)
先看下配置類的解析流程圖:
配置類解析流程圖
我們知道Spring容器的主要工作原理就是先根據配置的信息將相關的BeanDefinition(也就是Bean的元數據,比如類的全路徑名、屬性等)掃描并加載到容器中,后續再根據這些BeanDefinition對這些Bean進行實例化初始化等一系列操作,然后再將最后的Bean實例保存到Spring容器中。因此Springboot的自動配置的原理主要是BeanDefinition的自動注冊
我們就從Springboot的啟動類TomcatWarApplication開始分析:
package com.sourcecode.springboot.tomcatwar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TomcatWarApplication {
public static void main(String[] args) {
SpringApplication.run(TomcatWarApplication.class, args);
}
}
步驟分析:
第一步:main方法的作用就是新建一個SpringApplication實例并將將TomcatWarApplication.class放入到了該對象的實例屬性primarySources(一個Set)中,然后調用該對象的run方法
第二步:run方法中會對primarySources中的存放的TomcatWarApplication.class進行加載并解析為一個BeanDefinition后注冊到BeanFactory中
第三步【重點】:run方法內部的refreshContext方法會對BeanFactory中的BeanDefinition進行處理,將該BeanDefinition的注解元數據和對應的bean名稱封裝為一個ConfigurationClass(配置類)對象,然后對該對象進行解析。具體步驟如下:
- 類是否有@Component注解,如果有的話,解析該類的成員內部類是否滿足作為一個配置類的要求(該內部類是否有@Component、@ComponentScan、@Import、@ImportResource注解或者該內部類是否有被@Bean注解的方法),如果滿足的話,那么就將該內部類作為一個配置類進行同樣的解析
- 類是否有@PropertySource注解,用來解析properties配置文件中配置的屬性
- 類是否有@ComponentScan注解,如果有的話,解析該注解相應屬性中配置的包名、類的全路徑所在的包名,然后使用ClassPathBeanDefinitionScanner掃描器對這些類路徑下的class文件進行加載解析,判斷這些class是否滿足條件(是否有@Component注解、是否有@JAVAx.annotation.ManagedBean注解、是否有@javax.inject.Named注解),如果滿足條件的話,就將該class作為一個配置類進行同樣的解析
- 類是否有@Import注解,a)如果@Import注解中的value屬性值是ImportSelector類型的話,再判斷是否是延遲導入選擇器DeferredImportSelector類型的,如果是的話,那么延遲處理value屬性值對應的class,最終調用的是processGroupImports方法;如果不是延遲導入類型,那么立即對value屬性值對應的class進行處理,最終調用的是該類的selectImports方法,然后將該方法返回的所有的類名作為配置類進行同樣的解析;b)如果@Import注解中的value屬性值是ImportBeanDefinitionRegistrar類型的,則后續調用registerBeanDefinitions進行BeanDefinition的注冊;c)如果是其他類型的class,那么就直接將該class作為一個配置類進行同樣的解析
- 類是否有@ImportResource注解,如果有的話,就導入以XML格式進行BeanDefinition定義的配置文件,后續把該配置文件中定義的BeanDefinition作為配置類進行同樣的解析
- 類中是否有被@Bean注解的方法,如果有的話,抽取這些方法的元數據并添加到該配置類的屬性中,后續實例化時也會實例化以方法的形式聲明的Bean
- 如果類實現了接口,抽取這些接口中被@Bean注解了的默認方法(default method)的元數據并添加到該配置類的屬性中,后續實例化時也會實例化這些以方法的形式聲明的Bean
- 如果該類有父類的話,那么把父類相關信息也封裝為一個ConfigurationClass并做同上述一樣的解析
第四步:通過上述步驟之后,就把TomcatWarApplication.class以及它關聯的所有BeanDefinition注冊到BeanFactory中了,后面進行Bean的實例化的時候就會根據所有注冊的BeanDefinition來進行對應的實例化
回到我們的啟動類,我們將@SpringBootApplication的一些相關元注解全部提取出來放在一起,這些看起來清晰一些:
@Configuration
@Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationPackages.Registrar.class)
@ComponentScan
public class TomcatWarApplication {
public static void main(String[] args) {
SpringApplication.run(TomcatWarApplication.class, args);
}
}
這樣看發現我們的啟動類實際是被這些注解標識:
@Configuration
@Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationPackages.Registrar.class)
@ComponentScan
我們按我們上面步驟的第三步來對TomcatWarApplication.class這個配置類進行解析:
1、@ComponentScan滿足第三個條件,如果@ComponentScan注解沒有顯示設置的屬性,那么就掃描TomcatWarApplication所在包路徑(com.sourcecode.springboot.tomcatwar)下的class,如果滿足條件(比如注解了@Configuration或@Service等)就會將這些class作為一個配置類,然后進行同樣的解析
2、@Import(AutoConfigurationImportSelector.class),這個Selector是ImportSelector類型且是DeferredImportSelector類型,那么就將AutoConfigurationImportSelector中的selectImports方法返回的類作為配置類進行同樣解析,該方法使用SPI的方式從jar中的META-INF/spring.factories文件中獲取指定屬性的值(也就是對應類的全路徑名稱為屬性名),然后將這些類作為配置類進行同樣的解析,一些starter的定義就是使用這種方式(比如Druid數據源的starter),如下所示:
SPI
自動配置
druid數據源的starter
3、@Import(AutoConfigurationPackages.Registrar.class),該Registrar是ImportSelector類型但不是延遲導入類型,so調用該類的registerBeanDefinitions方法進行注冊BeanDefinition,比如Mybatis的注解@MapperScan:
@MapperScan注解
再來看@Import中的MapperScannerRegistrar,最終通過registerBeanDefinitions方法來對我們定義的Mapper/Dao進行掃描并注冊BeanDefinition到BeanFactory中
MapperScannerRegistrar
通過對Springboot自動配置的的分析,我們就可以定義我們自己組件的starter了~~~
再次回顧下配置類的解析流程圖吧:
配置類解析流程圖
覺得可以的話點個關注,加個收藏唄,陸續奉上干貨~~~~