SpringBoot自動配置原理分析
前言
SpringBoot
是我們經常使用的框架,那麼你能不能針對SpringBoot
實現自動配置做一個詳細的介紹。如果可以的話,能不能畫一下實現自動配置的流程圖。牽扯到哪些關鍵類,以及哪些關鍵點。下面我們一起來看看吧!!
閱讀完本文:
- 你能知道 SpringBoot 啟動時的自動配置的原理知識
- 你能知道 SpringBoot 啟動時的自動配置的流程
- 以及對於 SpringBoot 一些常用註解的瞭解
一步一步 debug 從淺到深。
註意
:本文的 SpringBoot 版本為 2.5.2
一、啟動類
前言什麼的,就不說瞭,大傢都會用的,我們直接從 SpringBoot
啟動類說起。
@SpringBootApplication public class Hello { public static void main(String[] args) { SpringApplication.run(Hello.class); } }
@SpringBootApplication
標註在某個類上說明這個類是 SpringBoot 的主配置類, SpringBoot 就應該運行這個類的main方法來啟動 SpringBoot 應用;是我們研究的重點!!!它的本質是一個組合註解,我們點進去,看看javadoc
上是怎麼寫的,分析從淺到深,從粗略到詳細。
我們點進去看:
@Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}
Javadoc
上是這麼寫的
表示聲明一個或多個@Bean方法並觸發 auto-configuration 和 component scanning 的 configuration 類。 這是一個方便的註解,相當於聲明瞭 @Configuration 、 @EnableAutoConfiguration 和@ComponentScan 。
—為什麼它能集成這麼多的註解的功能呢?
是在於它上面的 @Inherited
註解, @Inherited
表示自動繼承註解類型。
這裡的最重要的兩個註解是 @SpringBootConfiguration
和 @EnableAutoConfiguration
。
1.1、@SpringBootConfiguration
我們先點進去看看 @SpringBootConfiguration
註解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration {}。
1.2、@EnableAutoConfiguration
再看看 @EnableAutoConfiguration
.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
1.3、@ComponentScan
@ComponentScan
:配置用於 Configuration 類的組件掃描指令。 提供與 Spring XML
的 <context:component-scan>
元素並行的支持。 可以 basePackageClasses
或basePackages
( 或其別名value )來定義要掃描的特定包。 如果沒有定義特定的包,將從聲明該註解的類的包開始掃描。
作為瞭解,不是本文重點。
1.4、探究方向
主要探究圖中位於中間部分那條主線,其他隻會稍做講解。
二、@SpringBootConfiguration
我們剛剛已經簡單看瞭一下 @SpringBootConfiguration
啦。
@Configuration @Indexed public @interface SpringBootConfiguration {}
它是 springboot
的配置類,標註在某個類上,表示這是一個 springboot
的配置類。
我們在這看到 @Configuration
,這個註解我們在 Spring
中就已經看到過瞭,它的意思就是將一個類標註為 Spring
的配置類,相當於之前 Spring
中的 xml
文件,可以向容器中註入組件。
不是探究重點。
三、@EnableAutoConfiguration
我們來看看這玩意,它的字面意思就是:自動導入配置。
@Inherited @AutoConfigurationPackage ////自動導包 @Import(AutoConfigurationImportSelector.class) ////自動配置導入選擇 public @interface EnableAutoConfiguration {}
從這裡顧名思義就能猜到這裡肯定是跟自動配置有關系的。
我們接著來看看這上面的兩個註解 @AutoConfigurationPackage
和 @Import(AutoConfigurationImportSelector.class)
,這兩個才是我們研究的重點。
3.1、@AutoConfigurationPackage
點進去一看:
@Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
@Import
為 spring
的註解,導入一個配置文件,在 springboot
中為給容器導入一個組件,而導入的組件由 AutoConfigurationPackages.Registrar.class
執行邏輯來決定的。
往下👇看:Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } }
在這個地方我們可以打個斷點,看看 new PackageImports(metadata).getPackageNames().toArray(new String[0])
它是一個什麼值。
我們用 Evaluate
計算 new PackageImports(metadata).getPackageNames().toArray(new String[0])
出來可以看到就是 com.crush.hello
,當前啟動類所在的包。
繼續往下看的話就是和 Spring 註冊相關瞭,更深入 xdm 可以繼續 debug。
在這裡我們可以得到一個小小的結論:
@AutoConfigurationPackage 這個註解本身的含義就是將主配置類(@SpringBootApplication 標註的類)所在的包下面所有的組件都掃描到 spring 容器中。
如果將一個 Controller
放到 com.crush.hello
以外就不會被掃描到瞭,就會報錯。
3.2、@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector
開啟自動配置類的導包的選擇器(導入哪些組件的選擇器)
我們點進 AutoConfigurationImportSelector
類來看看,有哪些重點知識,這個類中存在方法可以幫我們獲取所有的配置
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { /**選擇需要導入的組件 ,*/ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //根據導入的@Configuration類的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry 。 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 可以在這打個斷點,看看 返回的數據 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //刪除重復項 configurations = removeDuplicates(configurations); // 排除依賴 Set<String> exclusions = getExclusions(annotationMetadata, attributes); //檢查 checkExcludedClasses(configurations, exclusions); //刪除需要排除的依賴 configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }
我們看看這個斷點,configurations
數組長度為131,並且文件後綴名都為 **AutoConfiguration
這裡的意思是將所有需要導入的組件以全類名的方式返回,並添加到容器中,最終會給容器中導入非常多的自動配置類(xxxAutoConfiguration),給容器中導入這個場景需要的所有組件,並配置好這些組件。有瞭自動配置,就不需要我們自己手寫瞭。
3.2.1、getCandidateConfigurations()
我們還需要思考一下,這些配置都從 getCandidateConfigurations 方法中獲取,這個方法可以用來獲取所有候選的配置,那麼這些候選的配置又是從哪來的呢?
一步一步點進去:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 這裡有個 loadFactoryNames 方法 執行的時候還傳瞭兩個參數,一個是BeanClassLoader ,另一個是 getSpringFactoriesLoaderFactoryClass() 我們一起看看 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
看一下getSpringFactoriesLoaderFactoryClass()
方法,這裡傳過去的是
protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
這個 EnableAutoConfiguration
是不是特別眼熟,(我們探究的起點 @EnableAutoConfiguration
,有沒有感覺自己離答案越來越近啦)
我們再看看 loadFactoryNames()
方法帶著它去做瞭什麼處理:
先是將 EnableAutoConfiguration.class
傳給瞭 factoryType
,然後 .getName( )
,所以factoryTypeName
值為 EnableAutoConfiguration
。
3.2.2、loadSpringFactories()
接下裡又開始調用 loadSpringFactories
方法
這裡的 FACTORIES_RESOURCE_LOCATION
在上面有定義:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
我們再回到 getCandidateConfigurations
方法處。
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
這句斷言的意思是:“在 META-INF/spring.factories 中沒有找到自動配置類。如果您使用自定義包裝,請確保該文件是正確的。“
這個 META-INF/spring.factories
在哪裡呢?
裡面的內容:
我們日常用到的,基本上都有一個配置類。
比如 webmvc,
我們點進 WebMvcProperties 類中去看一下:
那這裡到底是要幹什麼呢?
這裡的意思首先是把這個文件的 urls 拿到之後並把這些 urls 每一個遍歷,最終把這些文件整成一個properties 對象,loadProperties
方法
然後再從 properties 對象裡邊獲取一些我們需要的值,把這些獲取到的值來加載我們最終要返回的這個結果,結果 result 為 map 集合,然後返回到loadFactoryNames
方法中。
然後我們再回到 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
的調用處。
這個 factoryTypeName
值為 EnableAutoConfiguration
因為 loadFactoryNames
方法攜帶過來的第一個參數為 EnableAutoConfiguration.class
,所以 factoryType
值也為 EnableAutoConfiguration.class
,那麼 factoryTypeName
值為 EnableAutoConfiguration
。
那麼map集合中 getOrDefault
方法為什麼意思呢?意思就是當 Map
集合中有這個 key 時,就使用這個 key值,如果沒有就使用默認值 defaultValue
(第二個參數),所以是判斷是否包含 EnableAutoConfiguration
看下圖,這不就是嘛?
所以就是把 spring-boot-autoconfigure-2.5.2.jar/META-INF/spring.factories
這個文件下的EnableAutoConfiguration
下面所有的組件,每一個 xxxAutoConfiguration
類都是容器中的一個組件,都加入到容器中。加入到容器中之後的作用就是用它們來做自動配置,這就是Springboot
自動配置開始的地方。
隻有這些自動配置類進入到容器中以後,接下來這個自動配置類才開始進行啟動
那 spring.factories
中存在那麼多的配置,每次啟動時都是把它們全部加載嗎?
是全部加載嘛?不可能的哈,這誰都知道哈,全部加載啟動一個項目不知道要多久去瞭。它是有選擇的。
我們隨便點開一個類,都有這個 @ConditionalOnXXX
註解
@Conditional
其實是 spring
底層註解,意思就是根據不同的條件,來進行自己不同的條件判斷,如果滿足指定的條件,那麼整個配置類裡邊的配置才會生效。
所以在加載自動配置類的時候,並不是將 spring.factories
的配置全部加載進來,而是通過這個註解的判斷,如果註解中的類都存在,才會進行加載。
這就是SpringBoot
的自動配置啦.
四、小結
簡單總結起來就是:
啟動類中有一個 @SpringBootApplication
註解,包含瞭 @SpringBootConfiguration
、 @EnableAutoConfiguration
, @EnableAutoConfiguration
代表開啟自動裝配,註解會去 spring-boot-autoconfigure
工程下尋找 META-INF/spring.factories
文件,此文件中列舉瞭所有能夠自動裝配類的清單,然後自動讀取裡面的自動裝配配置類清單。因為有 @ConditionalOn
條件註解,滿足一定條件配置才會生效,否則不生效。 如: @ConditionalOnClass(某類.class)
工程中必須包含一些相關的類時,配置才會生效。所以說當我們的依賴中引入瞭一些對應的類之後,滿足瞭自動裝配的條件後,自動裝配才會被觸發。
到此這篇關於SpringBoot自動配置原理分析的文章就介紹到這瞭,更多相關SpringBoot自動配置內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java SpringBoot自動裝配原理詳解及源碼註釋
- SpringBoot自動配置與啟動流程詳細分析
- SpringBoot原理之自動配置機制詳解
- SpringBoot自動配置特點與原理詳細分析
- SpringBoot自動配置原理,你真的懂嗎?(簡單易懂)