SpringBoot作為當(dāng)前Java開發(fā)的熱門框架,有絞手架之稱。“約定大于配置”也一直是SpringBoot的標(biāo)簽,那么,SpringBoot要實(shí)現(xiàn)自身優(yōu)勢(shì),自動(dòng)配置功不可沒。
一、SpringBoot自動(dòng)配置是什么
SpringBoot自動(dòng)配置是指在應(yīng)用程序啟動(dòng)時(shí),SpringBoot根據(jù)classpath路徑下的jar包自動(dòng)配置應(yīng)用程序所需的一系列bean和組件,從而減少開發(fā)者的配置工作,提高開發(fā)效率。文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
二、@Import注解
在剖析SpringBoot自動(dòng)配置原理之前,我們先了解一下@Import注解的使用文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1. 方式一: .class方式
定義兩個(gè)類A、B,并將其加入到Spring IOC容器中:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Data public class A{ ???? private Integer id= 0 ; ???? private String name= "classA" ; ???? public void print(){ ???????? System.out.println( this .name); ???? } } @Data public class B{ ???? private Integer id= 1 ; ???? private String name= "classB" ; ???? public void print(){ ???????? System.out.println( this .name); ???? } } |
創(chuàng)建一個(gè)配置類,并使用@Import注解將類A、B添加到 IOC 容器中:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 | @Import ({A. class ,B. class }) @Configuration public class ClassConfig{ ???????} |
2.方式二:ImportSelector方式
該方法需要定義類來實(shí)現(xiàn)ImportSelector接口,并重寫其中的selectImports( )方法,該方法的返回值是需要添加到IOC容器中的類的全限定類名數(shù)組:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 7 8 | @Data public class C{ ???? private Integer id= 2 ; ???? private String name= "classC" ; ???? public void print(){ ???????? System.out.println( this .name); ???? } } |
編寫類來實(shí)現(xiàn)ImportSelector接口:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 | public class ImportSelectorTest implements ImportSelector{ ???? @Override ???? public String[] selectImports(AnnotationMetadata annotationMetada){ ???????? return new String[]{ "C.class" }; ???? } } |
使用該類:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 | @Import ({ImportSelectorTest. class }) @Configuration public class ImportSelectorConfig{ ???????} |
3.方式三:ImportBeanDefinitionRegistrar方式
定義一個(gè)類并實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口,重寫其中的registerBeanDefinitions( )方法,此方式可以自定義Bean在容器中的名稱:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Data public class D{ ???? private Integer id= 3 ; ???? private String name= "ClassD" ; ???? public void print(){ ???????? System.out.println( this .name); ???? } } //定義類實(shí)現(xiàn)ImPortBeanDefinitionRegistrar接口,重寫其中的registerBeanDefinitions()方法 public class ImportBeanDefinitionRegistrarTest{ ???? @Override ???? public void registerBeanDefninitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry registry{ ???????? RootBeanDefinition rootBeanDefinition= new RootBeanDefinition(D. class ); ???????? registry.registerBeanDefinition( "自定義名稱" ,rootBeanDefinition); ???? } } //使用上面的類進(jìn)行導(dǎo)入 @Import ({ImportBeanDefinitionRegistrarTest}) @Configuration public class ImportBeanDefinitionRegistrarConfig{ } |
三、SpringBoot自動(dòng)配置原理解析
為了容易分析和理解,我們?cè)贗DEA中創(chuàng)建一個(gè)SpringBoot項(xiàng)目,創(chuàng)建過程省略,直接跳到該項(xiàng)目的主配置類進(jìn)行分析:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 7 8 | @SpringBootApplication public class SpringBootTestApplication { ???? public static void main(String[] args) { ???????? SpringApplication.run(SpringBootTestApplication. class , args); ???? } } |
其中@SpringBootApplication注解是SpringBoot項(xiàng)目的重點(diǎn),按住ctrl鍵進(jìn)入其中,看到它由以下部分組成:文章源自四五設(shè)計(jì)網(wǎng)-http://www.wasochina.com/45484.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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 { ???? //內(nèi)容省略 } |
我們主要關(guān)注以下幾個(gè)注解:
- @SpringBootConfiguration:標(biāo)記當(dāng)前類為配置類
- @EnableAutoConfiuration:開啟自動(dòng)配置
- @ComponentScan:掃描主類所在包及其子包、同級(jí)包中的Bean
1、@SpringBootConfiguration注解:標(biāo)記當(dāng)前類為配置類
1 2 3 4 5 6 7 8 9 10 11 | @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration { ???? @AliasFor ( ???????? annotation = Configuration. class ???? ) ???? boolean proxyBeanMethods() default true ; } |
根據(jù)其源碼可以知道,@SpringBootConfiguration注解包含@Configuration,所以其擁有@Configuration注解相似的功能,而@Configuration注解又包含@Companent注解,所以配置類也存在于IOC容器中。
2、@EnableAutoConfiguration注解:開啟自動(dòng)配置
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import ({AutoConfigurationImportSelector. class }) public @interface EnableAutoConfiguration { ???? String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; ???? Class<?>[] exclude() default {}; ???? String[] excludeName() default {}; } |
根據(jù)其源碼得出其主要由@AutoConfigurationPackages注解和@Import注解組成
@AutoConfigurationPackages注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @Import ({Registrar. class }) public @interface AutoConfigurationPackage { ???? String[] basePackages() default {}; ???? Class<?>[] basePackageClasses() default {}; } //其中的@Import注解導(dǎo)入了Registrar類,該類是AutoConfigurationPackages的靜態(tài)類部類 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { ???????? Registrar() { ???????? } ???????? /** ???????????? 根據(jù)傳入的元注解信息獲取所在的包,將包中組件類封裝為數(shù)組進(jìn)行注冊(cè) ???????? */ ???????? public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ???????????? 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)); ???????? } ???? } |
@Import({AutoConfigurationImportSelector.class})注解
這一步就用到了@Import注解使用方式中的第二中:實(shí)現(xiàn)ImportSelector接口
那么AutoConfigurationImportSelector接口何時(shí)如何被執(zhí)行呢?
SpringBoot 啟動(dòng)時(shí)會(huì)使用 ConfigurationClassParser 來解析被 @Configuration 標(biāo)識(shí)的配置類, 然后再處理這個(gè)類內(nèi)部被其他注解修飾的情況, 比如 @Import 注解, @ComponentScan 注解,@Bean 注解等
若發(fā)現(xiàn)注解中存在 @Import(ImportSelector) 的情況下,就會(huì)創(chuàng)建一個(gè)相應(yīng)的 ImportSelector 對(duì)象,并調(diào)用其 process 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { ???????????? Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> { ???????????????? return String.format( "Only %s implementations are supported, got %s" , AutoConfigurationImportSelector. class .getSimpleName(), deferredImportSelector.getClass().getName()); ???????????? }); ???????????? AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); ???????????? this .autoConfigurationEntries.add(autoConfigurationEntry); ???????????? Iterator var4 = autoConfigurationEntry.getConfigurations().iterator(); ???????????? while (var4.hasNext()) { ???????????????? String importClassName = (String)var4.next(); ???????????????? this .entries.putIfAbsent(importClassName, annotationMetadata); ???????????? } ???????? } |
process方法又調(diào)用了getAutoConfigurationEntry方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { ???????? if (! this .isEnabled(annotationMetadata)) { ???????????? return EMPTY_ENTRY; ???????? } else { ???????????? AnnotationAttributes attributes = this .getAttributes(annotationMetadata); ???????????? List<String> configurations = this .getCandidateConfigurations(annotationMetadata, attributes); ???????????? 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); ???????? } ???? } |
getAutoConfigurationEntry方法調(diào)用了getCandidateConfigurations方法:
1 2 3 4 5 | 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; ???? } |
getCandidateConfigurations方法中使用了Spring Cor中的類SpringFactoriesLoader,該類的loadFactoryNames方法可以根據(jù)接口獲取接口類的名稱,這個(gè)方法返回的是類名的列表,loadFactoryNames方法會(huì)遍歷整個(gè)springboot項(xiàng)目的classpath下的ClassLoader中所有jar包下的spring.factories文件。至此自動(dòng)配置結(jié)束
總結(jié)
SpringBoot自動(dòng)配置是SpringBoot的核心,所以了解SpringBoot的自動(dòng)配置是非常有必要的,大家可以自行查找資料解釋以下為什么不使用@ComponentScan注解替換@Import注解來進(jìn)行類的導(dǎo)入


評(píng)論