SpringBoot自動(dòng)配置原理分析
前言
SpringBoot是我們經(jīng)常使用的框架,那么你能不能針對(duì)SpringBoot實(shí)現(xiàn)自動(dòng)配置做一個(gè)詳細(xì)的介紹。如果可以的話,能不能畫一下實(shí)現(xiàn)自動(dòng)配置的流程圖。牽扯到哪些關(guān)鍵類,以及哪些關(guān)鍵點(diǎn)。下面我們一起來(lái)看看吧??!
閱讀完本文:
- 你能知道 SpringBoot 啟動(dòng)時(shí)的自動(dòng)配置的原理知識(shí)
- 你能知道 SpringBoot 啟動(dòng)時(shí)的自動(dòng)配置的流程
- 以及對(duì)于 SpringBoot 一些常用注解的了解
一步一步 debug 從淺到深。
注意:本文的 SpringBoot 版本為 2.5.2
一、啟動(dòng)類
前言什么的,就不說(shuō)了,大家都會(huì)用的,我們直接從 SpringBoot 啟動(dòng)類說(shuō)起。
@SpringBootApplication
public class Hello {
public static void main(String[] args) {
SpringApplication.run(Hello.class);
}
}@SpringBootApplication 標(biāo)注在某個(gè)類上說(shuō)明這個(gè)類是 SpringBoot 的主配置類, SpringBoot 就應(yīng)該運(yùn)行這個(gè)類的main方法來(lái)啟動(dòng) SpringBoot 應(yīng)用;是我們研究的重點(diǎn)?。?!它的本質(zhì)是一個(gè)組合注解,我們點(diǎn)進(jìn)去,看看javadoc上是怎么寫的,分析從淺到深,從粗略到詳細(xì)。
我們點(diǎn)進(jìn)去看:
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}Javadoc上是這么寫的
表示聲明一個(gè)或多個(gè)@Bean方法并觸發(fā) auto-configuration 和 component scanning 的 configuration 類。 這是一個(gè)方便的注解,相當(dāng)于聲明了 @Configuration 、 @EnableAutoConfiguration 和@ComponentScan 。
---為什么它能集成這么多的注解的功能呢?
是在于它上面的 @Inherited 注解, @Inherited 表示自動(dòng)繼承注解類型。
這里的最重要的兩個(gè)注解是 @SpringBootConfiguration 和 @EnableAutoConfiguration。
1.1、@SpringBootConfiguration
我們先點(diǎn)進(jìn)去看看 @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 )來(lái)定義要掃描的特定包。 如果沒(méi)有定義特定的包,將從聲明該注解的類的包開(kāi)始掃描。
作為了解,不是本文重點(diǎn)。
1.4、探究方向

主要探究圖中位于中間部分那條主線,其他只會(huì)稍做講解。
二、@SpringBootConfiguration
我們剛剛已經(jīng)簡(jiǎn)單看了一下 @SpringBootConfiguration 啦。
@Configuration
@Indexed
public @interface SpringBootConfiguration {}它是 springboot 的配置類,標(biāo)注在某個(gè)類上,表示這是一個(gè) springboot的配置類。
我們?cè)谶@看到 @Configuration ,這個(gè)注解我們?cè)?nbsp;Spring 中就已經(jīng)看到過(guò)了,它的意思就是將一個(gè)類標(biāo)注為 Spring 的配置類,相當(dāng)于之前 Spring 中的 xml 文件,可以向容器中注入組件。
不是探究重點(diǎn)。
三、@EnableAutoConfiguration
我們來(lái)看看這玩意,它的字面意思就是:自動(dòng)導(dǎo)入配置。
@Inherited
@AutoConfigurationPackage ////自動(dòng)導(dǎo)包
@Import(AutoConfigurationImportSelector.class) ////自動(dòng)配置導(dǎo)入選擇
public @interface EnableAutoConfiguration {}從這里顧名思義就能猜到這里肯定是跟自動(dòng)配置有關(guān)系的。
我們接著來(lái)看看這上面的兩個(gè)注解 @AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class) ,這兩個(gè)才是我們研究的重點(diǎn)。
3.1、@AutoConfigurationPackage
點(diǎn)進(jìn)去一看:
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}@Import 為 spring 的注解,導(dǎo)入一個(gè)配置文件,在 springboot 中為給容器導(dǎo)入一個(gè)組件,而導(dǎo)入的組件由 AutoConfigurationPackages.Registrar.class 執(zhí)行邏輯來(lái)決定的。
往下??看: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));
}
}在這個(gè)地方我們可以打個(gè)斷點(diǎn),看看 new PackageImports(metadata).getPackageNames().toArray(new String[0]) 它是一個(gè)什么值。

我們用 Evaluate 計(jì)算 new PackageImports(metadata).getPackageNames().toArray(new String[0]) 出來(lái)可以看到就是 com.crush.hello ,當(dāng)前啟動(dòng)類所在的包。
繼續(xù)往下看的話就是和 Spring 注冊(cè)相關(guān)了,更深入 xdm 可以繼續(xù) debug。
在這里我們可以得到一個(gè)小小的結(jié)論:
@AutoConfigurationPackage 這個(gè)注解本身的含義就是將主配置類(@SpringBootApplication 標(biāo)注的類)所在的包下面所有的組件都掃描到 spring 容器中。
如果將一個(gè) Controller 放到 com.crush.hello 以外就不會(huì)被掃描到了,就會(huì)報(bào)錯(cuò)。
3.2、@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector 開(kāi)啟自動(dòng)配置類的導(dǎo)包的選擇器(導(dǎo)入哪些組件的選擇器)
我們點(diǎn)進(jìn) AutoConfigurationImportSelector 類來(lái)看看,有哪些重點(diǎn)知識(shí),這個(gè)類中存在方法可以幫我們獲取所有的配置
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
/**選擇需要導(dǎo)入的組件 ,*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//根據(jù)導(dǎo)入的@Configuration類的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry 。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 可以在這打個(gè)斷點(diǎn),看看 返回的數(shù)據(jù)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//刪除重復(fù)項(xiàng)
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);
}
}我們看看這個(gè)斷點(diǎn),configurations 數(shù)組長(zhǎng)度為131,并且文件后綴名都為 **AutoConfiguration

這里的意思是將所有需要導(dǎo)入的組件以全類名的方式返回,并添加到容器中,最終會(huì)給容器中導(dǎo)入非常多的自動(dòng)配置類(xxxAutoConfiguration),給容器中導(dǎo)入這個(gè)場(chǎng)景需要的所有組件,并配置好這些組件。有了自動(dòng)配置,就不需要我們自己手寫了。
3.2.1、getCandidateConfigurations()
我們還需要思考一下,這些配置都從 getCandidateConfigurations 方法中獲取,這個(gè)方法可以用來(lái)獲取所有候選的配置,那么這些候選的配置又是從哪來(lái)的呢?
一步一步點(diǎn)進(jìn)去:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 這里有個(gè) loadFactoryNames 方法 執(zhí)行的時(shí)候還傳了兩個(gè)參數(shù),一個(gè)是BeanClassLoader ,另一個(gè)是 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() 方法,這里傳過(guò)去的是
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}這個(gè) EnableAutoConfiguration 是不是特別眼熟,(我們探究的起點(diǎn) @EnableAutoConfiguration ,有沒(méi)有感覺(jué)自己離答案越來(lái)越近啦)
我們?cè)倏纯?nbsp;loadFactoryNames() 方法帶著它去做了什么處理:

先是將 EnableAutoConfiguration.class 傳給了 factoryType ,然后 .getName( ) ,所以factoryTypeName 值為 EnableAutoConfiguration。
3.2.2、loadSpringFactories()
接下里又開(kāi)始調(diào)用 loadSpringFactories 方法

這里的 FACTORIES_RESOURCE_LOCATION 在上面有定義:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
我們?cè)倩氐?nbsp;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 中沒(méi)有找到自動(dòng)配置類。如果您使用自定義包裝,請(qǐng)確保該文件是正確的。“
這個(gè) META-INF/spring.factories 在哪里呢?

里面的內(nèi)容:

我們?nèi)粘S玫降?,基本上都有一個(gè)配置類。
比如 webmvc,

我們點(diǎn)進(jìn) WebMvcProperties 類中去看一下:

那這里到底是要干什么呢?

這里的意思首先是把這個(gè)文件的 urls 拿到之后并把這些 urls 每一個(gè)遍歷,最終把這些文件整成一個(gè)properties 對(duì)象,loadProperties方法

然后再?gòu)?properties 對(duì)象里邊獲取一些我們需要的值,把這些獲取到的值來(lái)加載我們最終要返回的這個(gè)結(jié)果,結(jié)果 result 為 map 集合,然后返回到loadFactoryNames方法中。
然后我們?cè)倩氐?nbsp; loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); 的調(diào)用處。

這個(gè) factoryTypeName 值為 EnableAutoConfiguration
因?yàn)?nbsp;loadFactoryNames 方法攜帶過(guò)來(lái)的第一個(gè)參數(shù)為 EnableAutoConfiguration.class,所以 factoryType 值也為 EnableAutoConfiguration.class,那么 factoryTypeName 值為 EnableAutoConfiguration。

那么map集合中 getOrDefault 方法為什么意思呢?意思就是當(dāng) Map 集合中有這個(gè) key 時(shí),就使用這個(gè) key值,如果沒(méi)有就使用默認(rèn)值 defaultValue (第二個(gè)參數(shù)),所以是判斷是否包含 EnableAutoConfiguration
看下圖,這不就是嘛?

所以就是把 spring-boot-autoconfigure-2.5.2.jar/META-INF/spring.factories 這個(gè)文件下的EnableAutoConfiguration 下面所有的組件,每一個(gè) xxxAutoConfiguration 類都是容器中的一個(gè)組件,都加入到容器中。加入到容器中之后的作用就是用它們來(lái)做自動(dòng)配置,這就是Springboot自動(dòng)配置開(kāi)始的地方。
只有這些自動(dòng)配置類進(jìn)入到容器中以后,接下來(lái)這個(gè)自動(dòng)配置類才開(kāi)始進(jìn)行啟動(dòng)
那 spring.factories 中存在那么多的配置,每次啟動(dòng)時(shí)都是把它們?nèi)考虞d嗎?
是全部加載嘛?不可能的哈,這誰(shuí)都知道哈,全部加載啟動(dòng)一個(gè)項(xiàng)目不知道要多久去了。它是有選擇的。
我們隨便點(diǎn)開(kāi)一個(gè)類,都有這個(gè) @ConditionalOnXXX 注解

@Conditional 其實(shí)是 spring 底層注解,意思就是根據(jù)不同的條件,來(lái)進(jìn)行自己不同的條件判斷,如果滿足指定的條件,那么整個(gè)配置類里邊的配置才會(huì)生效。
所以在加載自動(dòng)配置類的時(shí)候,并不是將 spring.factories 的配置全部加載進(jìn)來(lái),而是通過(guò)這個(gè)注解的判斷,如果注解中的類都存在,才會(huì)進(jìn)行加載。
這就是SpringBoot的自動(dòng)配置啦.
四、小結(jié)
簡(jiǎn)單總結(jié)起來(lái)就是:
啟動(dòng)類中有一個(gè) @SpringBootApplication 注解,包含了 @SpringBootConfiguration、 @EnableAutoConfiguration , @EnableAutoConfiguration 代表開(kāi)啟自動(dòng)裝配,注解會(huì)去 spring-boot-autoconfigure 工程下尋找 META-INF/spring.factories 文件,此文件中列舉了所有能夠自動(dòng)裝配類的清單,然后自動(dòng)讀取里面的自動(dòng)裝配配置類清單。因?yàn)橛?nbsp;@ConditionalOn 條件注解,滿足一定條件配置才會(huì)生效,否則不生效。 如: @ConditionalOnClass(某類.class) 工程中必須包含一些相關(guān)的類時(shí),配置才會(huì)生效。所以說(shuō)當(dāng)我們的依賴中引入了一些對(duì)應(yīng)的類之后,滿足了自動(dòng)裝配的條件后,自動(dòng)裝配才會(huì)被觸發(fā)。

到此這篇關(guān)于SpringBoot自動(dòng)配置原理分析的文章就介紹到這了,更多相關(guān)SpringBoot自動(dòng)配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC學(xué)習(xí)教程之RequestMappingHandlerMapping匹配
這篇文章主要給大家介紹了關(guān)于Spring MVC學(xué)習(xí)教程之RequestMappingHandlerMapping匹配的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
Activiti開(kāi)發(fā)環(huán)境的配置
本篇文章主要內(nèi)容介紹了Activiti開(kāi)發(fā)環(huán)境的配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Spring Cloud Feign實(shí)現(xiàn)動(dòng)態(tài)URL
本文主要介紹了Spring Cloud Feign實(shí)現(xiàn)動(dòng)態(tài)URL,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

