SpringBoot自動(dòng)配置原理詳解
閱讀收獲
理解SpringBoot自動(dòng)配置原理
一、SpringBoot是什么
SpringBoot 的誕生就是為了簡(jiǎn)化 Spring 中繁瑣的 XML 配置,其本質(zhì)依然還是Spring框架,使用SpringBoot之后可以不使用任何 XML 配置來(lái)啟動(dòng)一個(gè)服務(wù),使得我們?cè)谑褂梦⒎?wù)架構(gòu)時(shí)可以更加快速的建立一個(gè)應(yīng)用。
簡(jiǎn)單來(lái)說(shuō)就是SpringBoot其實(shí)不是什么新的框架,它默認(rèn)配置了很多框架的使用方式。
二、SpringBoot的特點(diǎn)
- 提供了固定的配置來(lái)簡(jiǎn)化配置,即約定大約配置
- 盡可能地自動(dòng)配置 Spring 和第三方庫(kù),即能自動(dòng)裝配
- 內(nèi)嵌容器,創(chuàng)建獨(dú)立的 Spring 應(yīng)用
- 讓測(cè)試變的簡(jiǎn)單,內(nèi)置了JUnit、Spring Boot Test等多種測(cè)試框架,方便測(cè)試
- 提供可用于生產(chǎn)的特性,如度量、運(yùn)行狀況檢查和外部化配置。
- 完全不需要生成代碼,也不需要 XML 配置。
三、啟動(dòng)類
下面探究SpringBoot的啟動(dòng)原理,關(guān)于一些細(xì)節(jié)就不贅述,我們捉住主線分析即可。
注意:本文的 SpringBoot 版本為 2.6.1
3.1 @SpringBootApplication
一切的來(lái)自起源SpringBoot的啟動(dòng)類,我們發(fā)現(xiàn)main方法上面有個(gè)注解:@SpringBootApplication
@SpringBootApplication public class SpringbootWorkApplication { public static void main(String[] args) { SpringApplication.run(SpringbootWorkApplication.class, args); } }
@SpringBootApplication 標(biāo)注在某個(gè)類上說(shuō)明這個(gè)類是 SpringBoot 的主配置類, SpringBoot 就應(yīng)該運(yùn)行這個(gè)類的main方法來(lái)啟動(dòng) SpringBoot 應(yīng)用;它的本質(zhì)是一個(gè)組合注解,我們點(diǎn)進(jìn)去查看該類的元信息主要包含3個(gè)注解:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication {
@SpringBootConfiguration
(里面就是@Configuration,標(biāo)注當(dāng)前類為配置類,其實(shí)只是做了一層封裝改了個(gè)名字而已)@EnableAutoConfiguration
(開(kāi)啟自動(dòng)配置)@ComponentScan
(包掃描)
注:@Inherited是一個(gè)標(biāo)識(shí),用來(lái)修飾注解,如果一個(gè)類用上了@Inherited修飾的注解,那么其子類也會(huì)繼承這個(gè)注解
我們下面逐一分析這3個(gè)注解作用
3.1.1 @SpringBootConfiguration
我們繼續(xù)點(diǎn)@SpringBootConfiguration進(jìn)去查看源碼如下:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
@Configuration標(biāo)注在某個(gè)類上,表示這是一個(gè) springboot的配置類??梢韵蛉萜髦凶⑷虢M件。
3.1.2 @ComponentScan
- @ComponentScan:配置用于 Configuration 類的組件掃描指令。
- 提供與 Spring XML 的 <context:component-scan> 元素并行的支持。
- 可以 basePackageClasses 或basePackages 來(lái)定義要掃描的特定包。 如果沒(méi)有定義特定的包,將從聲明該注解的類的包開(kāi)始掃描。
3.1.3 @EnableAutoConfiguration
- @EnableAutoConfiguration顧名思義就是:開(kāi)啟自動(dòng)導(dǎo)入配置
- 這個(gè)注解是SpringBoot的重點(diǎn),我們下面詳細(xì)講解
四、@EnableAutoConfiguration
我們點(diǎn)進(jìn)去看看該注解有什么內(nèi)容
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage //自動(dòng)導(dǎo)包 @Import({AutoConfigurationImportSelector.class}) //自動(dòng)配置導(dǎo)入選擇 public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
4.1 @AutoConfigurationPackage
自動(dòng)導(dǎo)入配置包
點(diǎn)進(jìn)去查看代碼:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
@Import 為spring的注解,導(dǎo)入一個(gè)配置文件,在springboot中為給容器導(dǎo)入一個(gè)組件,而導(dǎo)入的組件由 AutoConfigurationPackages.class的內(nèi)部類Registrar.class 執(zhí)行邏輯來(lái)決定是如何導(dǎo)入的。
4.1.1 @Import({Registrar.class})
點(diǎn)Registrar.class進(jìn)去查看源碼如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //斷點(diǎn) AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); } }
注:Registrar實(shí)現(xiàn)了ImportBeanDefinitionRegistrar類,就可以被注解@Import導(dǎo)入到spring容器里。
這個(gè)地方打斷點(diǎn)
運(yùn)行可以查看到(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的值為com.ljw.springbootwork:當(dāng)前啟動(dòng)類所在的包名
結(jié)論:@AutoConfigurationPackage 就是將主配置類(@SpringBootApplication 標(biāo)注的類)所在的包下面所有的組件都掃描注冊(cè)到 spring 容器中。
4.2? @Import({AutoConfigurationImportSelector.class})
作用:AutoConfigurationImportSelector開(kāi)啟自動(dòng)配置類的導(dǎo)包的選擇器,即是帶入哪些類,有選擇性的導(dǎo)入
點(diǎn)AutoConfigurationImportSelector.class進(jìn)入查看源碼,這個(gè)類中有兩個(gè)方法見(jiàn)名知意:
1.selectImports:選擇需要導(dǎo)入的組件
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
2.getAutoConfigurationEntry:根據(jù)導(dǎo)入的@Configuration類的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); // 這打個(gè)斷點(diǎn),看看 返回的數(shù)據(jù) List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //刪除重復(fù)項(xiàng) configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); //檢查 this.checkExcludedClasses(configurations, exclusions); //刪除需要排除的依賴 configurations.removeAll(exclusions); configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
this.getCandidateConfigurations(annotationMetadata, attributes)這里斷點(diǎn)查看
configurations數(shù)組長(zhǎng)度為133,并且文件后綴名都為 **AutoConfiguration
結(jié)論: 這些都是候選的配置類,經(jīng)過(guò)去重,去除需要的排除的依賴,最終的組件才是這個(gè)環(huán)境需要的所有組件。有了自動(dòng)配置,就不需要我們自己手寫(xiě)配置的值了,配置類有默認(rèn)值的。
我們繼續(xù)往下看看是如何返回需要配置的組件的
4.2.1 getCandidateConfigurations(annotationMetadata, attributes)
方法如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; }
這里有句斷言: 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)確保該文件是正確的?!?/p>
結(jié)論: 即是要loadFactoryNames()方法要找到自動(dòng)的配置類返回才不會(huì)報(bào)錯(cuò)。
4.2.1.1 getSpringFactoriesLoaderFactoryClass()
我們點(diǎn)進(jìn)去發(fā)現(xiàn):this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class這個(gè)注解。這個(gè)注解和@SpringBootApplication下標(biāo)識(shí)注解是同一個(gè)注解。
protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
結(jié)論:獲取一個(gè)能加載自動(dòng)配置類的類,即SpringBoot默認(rèn)自動(dòng)配置類為EnableAutoConfiguration
4.2.2 SpringFactoriesLoader
SpringFactoriesLoader工廠加載機(jī)制是Spring內(nèi)部提供的一個(gè)約定俗成的加載方式,只需要在模塊的META-INF/spring.factories文件,這個(gè)Properties格式的文件中的key是接口、注解、或抽象類的全名,value是以逗號(hào) “ , “ 分隔的實(shí)現(xiàn)類,使用SpringFactoriesLoader來(lái)實(shí)現(xiàn)相應(yīng)的實(shí)現(xiàn)類注入Spirng容器中。
注:會(huì)加載所有jar包下的classpath路徑下的META-INF/spring.factories文件,這樣文件不止一個(gè)。
4.2.2.1 loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
斷點(diǎn)查看factoryTypeName:
先是將 EnableAutoConfiguration.class 傳給了 factoryType?
然后String factoryTypeName = factoryType.getName();,所以factoryTypeName 值為? org.springframework.boot.autoconfigure.EnableAutoConfiguration
4.2.2.2 loadSpringFactories()
接著查看loadSpringFactories方法的作用
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { //斷點(diǎn)查看 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //注意這里:META-INF/spring.factories Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { //斷點(diǎn) result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements //去重,斷點(diǎn)查看result值 result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
這里的 FACTORIES_RESOURCE_LOCATION 在上面有定義:META-INF/spring.factories
public final class SpringFactoriesLoader { /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
META-INF/spring.factories文件在哪里??
在所有引入的java包的當(dāng)前類路徑下的META-INF/spring.factories文件都會(huì)被讀取,如:
斷點(diǎn)查看result值如下:
該方法作用是加載所有依賴的路徑META-INF/spring.factories文件,通過(guò)map結(jié)構(gòu)保存,key為文件中定義的一些標(biāo)識(shí)工廠類,value就是能自動(dòng)配置的一些工廠實(shí)現(xiàn)的類,value用list保存并去重。
在回看 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
因?yàn)?loadFactoryNames 方法攜帶過(guò)來(lái)的第一個(gè)參數(shù)為 EnableAutoConfiguration.class,所以 factoryType 值也為 EnableAutoConfiguration.class,那么 factoryTypeName 值為 EnableAutoConfiguration。拿到的值就是META-INF/spring.factories文件下的key為
org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
getOrDefault 當(dāng) Map 集合中有這個(gè) key 時(shí),就使用這個(gè) key值,如果沒(méi)有就使用默認(rèn)值空數(shù)組
結(jié)論:
- loadSpringFactories()該方法就是從“META-INF/spring.factories”中加載給定類型的工廠實(shí)現(xiàn)的完全限定類名放到map中
- loadFactoryNames()是根據(jù)SpringBoot的啟動(dòng)生命流程,當(dāng)需要加載自動(dòng)配置類時(shí),就會(huì)傳入org.springframework.boot.autoconfigure.EnableAutoConfiguration參數(shù),從map中查找key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,這些值通過(guò)反射加到容器中,之后的作用就是用它們來(lái)做自動(dòng)配置,這就是Springboot自動(dòng)配置開(kāi)始的地方
- 只有這些自動(dòng)配置類進(jìn)入到容器中以后,接下來(lái)這個(gè)自動(dòng)配置類才開(kāi)始進(jìn)行啟動(dòng)
- 當(dāng)需要其他的配置時(shí)如監(jiān)聽(tīng)相關(guān)配置:listenter,就傳不同的參數(shù),獲取相關(guān)的listenter配置。
五、流程總結(jié)圖
六、常用的Conditional注解
在加載自動(dòng)配置類的時(shí)候,并不是將spring.factories的配置全部加載進(jìn)來(lái),而是通過(guò)@Conditional等注解的判斷進(jìn)行動(dòng)態(tài)加載
@Conditional其實(shí)是spring底層注解,意思就是根據(jù)不同的條件,來(lái)進(jìn)行自己不同的條件判斷,如果滿足指定的條件,那么配置類里邊的配置才會(huì)生效。
常用的Conditional注解:
- @ConditionalOnClass : classpath中存在該類時(shí)起效
- @ConditionalOnMissingClass : classpath中不存在該類時(shí)起效
- @ConditionalOnBean : DI容器中存在該類型Bean時(shí)起效
- @ConditionalOnMissingBean : DI容器中不存在該類型Bean時(shí)起效
- @ConditionalOnSingleCandidate : DI容器中該類型Bean只有一個(gè)或@Primary的只有一個(gè)時(shí)起效
- @ConditionalOnExpression : SpEL表達(dá)式結(jié)果為true時(shí)
- @ConditionalOnProperty : 參數(shù)設(shè)置或者值一致時(shí)起效
- @ConditionalOnResource : 指定的文件存在時(shí)起效
- @ConditionalOnJndi : 指定的JNDI存在時(shí)起效
- @ConditionalOnJava : 指定的Java版本存在時(shí)起效
- @ConditionalOnWebApplication : Web應(yīng)用環(huán)境下起效
- @ConditionalOnNotWebApplication : 非Web應(yīng)用環(huán)境下起效
七、@Import支持導(dǎo)入的三種方式
1.帶有@Configuration的配置類
2.ImportSelector 的實(shí)現(xiàn)
3.ImportBeanDefinitionRegistrar 的實(shí)現(xiàn)
以上就是SpringBoot自動(dòng)配置原理詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot自動(dòng)配置原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java SpringBoot自動(dòng)配置原理詳情
- Springboot自動(dòng)配置與@Configuration配置類詳解
- SpringBoot自動(dòng)配置實(shí)現(xiàn)的詳細(xì)步驟
- SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟
- springboot自動(dòng)配置原理以及spring.factories文件的作用詳解
- springboot自動(dòng)配置原理解析
- 淺談springboot自動(dòng)配置原理
- SpringBoot自動(dòng)配置的實(shí)現(xiàn)原理
- Springboot的自動(dòng)配置是什么及注意事項(xiàng)
相關(guān)文章
Java web.xml之contextConfigLocation作用案例詳解
這篇文章主要介紹了Java web.xml之contextConfigLocation作用案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringMVC的處理器適配器-HandlerAdapter的用法及說(shuō)明
這篇文章主要介紹了SpringMVC的處理器適配器-HandlerAdapter的用法及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12java之StringBuffer常見(jiàn)使用方法解析
這篇文章主要介紹了java之StringBuffer常見(jiàn)使用方法解析,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Mybatis動(dòng)態(tài)sql超詳細(xì)講解
動(dòng)態(tài)SQL是MyBatis的強(qiáng)大特性之一,顧名思義就是會(huì)動(dòng)的SQL,即是能夠靈活的根據(jù)某種條件拼接出完整的SQL語(yǔ)句,下面這篇文章主要給大家介紹了關(guān)于Mybatis動(dòng)態(tài)sql的相關(guān)資料,需要的朋友可以參考下2023-04-04Spring AOP 切面@Around注解的用法說(shuō)明
這篇文章主要介紹了Spring AOP 切面@Around注解的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02關(guān)于MyBatis 查詢數(shù)據(jù)時(shí)屬性中多對(duì)一的問(wèn)題(多條數(shù)據(jù)對(duì)應(yīng)一條數(shù)據(jù))
這篇文章主要介紹了MyBatis 查詢數(shù)據(jù)時(shí)屬性中多對(duì)一的問(wèn)題(多條數(shù)據(jù)對(duì)應(yīng)一條數(shù)據(jù)),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Java中的靜態(tài)綁定和動(dòng)態(tài)綁定詳細(xì)介紹
這篇文章主要介紹了Java中的靜態(tài)綁定和動(dòng)態(tài)綁定詳細(xì)介紹,在Java中存在兩種綁定方式,一種為靜態(tài)綁定,又稱作早期綁定,另一種就是動(dòng)態(tài)綁定,亦稱為后期綁定,需要的朋友可以參考下2015-01-01