0. 简介
学习SpringBoot自动配置原理的时候,学得很迷糊,于是自己搜索资料整理思路。本文为作者一天的成果,搜索查看了大量的资料(建议有的时候还是要自己打打断点看看源码比较好),发现网上大部分都讲得很没用,或者讲得很浅。源代码有一些我也无法理解,但是通过打断点获知其方法调用的返回结果渐渐搞清楚方法的作用,整理如下。
参开资料如下:
一文搞懂🔥SpringBoot自动配置原理
Spring Boot面试杀手锏————自动配置原理
@Import注解的作用
1. SprintBoot的自动配置原理:
SpringBoot启动类的注解@SpringBootApplication
中,起到关键作用的注解是@EnableAutoConfiguration
。而@EnableAutoConfiguration
也是一个复合注解,它包含了@AutoConfigurationPackage
和@Import({AutoConfigurationImportSelector.class})
。
-
@AutoConfigurationPackage
:@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class})
@Import
为spring的注解,用来导入配置类或者一些需要前置加载的类,通过进一步点击查看源代码,可以看到Registrar.class
实现了ImportBeanDefinitionRegistrar
接口,通过@Import
导入该接口的实现类会执行重写的registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
方法。static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } 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)); } }
通过断点测试,我们可以发现
(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])
的结果是当前启动类所在的包名。而AutoConfigurationPackages.register(BeanDefinitionRegistry registry, String... packageNames)
方法的作用是将启动类所在包名/路径名加入容器中,一个专门记录packageNames的集合里。容器会扫描该集合下的路径名,从而去导入相应的组件。即,@AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。
-
@Import({AutoConfigurationImportSelector.class})
:@Import
注解导入的该类,实现了DeferredImportSelector
接口,而DeferredImportSelector
又实现了ImportSelector
接口,因此该类实现了ImportSelector
接口。通过@Import
导入的该接口的实现类会执行重写的selectImports(AnnotationMetadata annotationMetadata)
方法,该方法的返回值是String[]
类型,即需要自动配置/导入Spring容器中的类的全限定名。public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
NO_IMPORTS
是new String[0]
,因此我们关注else部分,而else部分只有两条语句,重点在第一条语句对于getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
的调用。protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); // 获取所有jar包下的 META-INF/spring.factories 文件定义中,对应的 // EnableAutoConfiguration 部分作为候选配置 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); // 这里的作用是,过滤掉一些不应该生效的自动配置 // 部分自动配置类会添加注解:@OnXXXCondition,表示满足xx条件下才可以加入自动配置 configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); }
这里点进去
getCandidateConfigurations(annotationMetadata, annotationAttributes)
方法,可以看到方法体中调用另外一个方法loadFactoryNames(factoryType, classLoader)
,其中factoryType
的值为EnableAutoConfiguration
,classLoader
的值为AppClassLoader
(打断点可以观察到)。loadFactoryNames(factoryType, classLoader)
最终返回另外一个方法调用的筛选结果,即loadSpringFactories(classLoader)
,点进去该方法的源码,在该位置打断点进行观察:可以看到返回值result的结果是一个key-value保存的Map,展开如下。其中就有我们需要并且由
loadFactoryNames(factoryType, classLoader)
方法返回的EnableAutoConfiguration
部分。loadSpringFactories(classLoader)
遍历了所有jar包路径下的META-INF/spring.factories
文件,这些文件给Spring中的容器组件进行了相关的设置,并汇总成为一个Map集合,最后返回。从内往外推的顺序如下:
-
loadSpringFactories()
方法加载所有jar包下的META-INF/spring.factories
文件中的配置,并将其整理为Map,传递出去; -
loadFactoryNames()
接收到loadSpringFactories()
的调用结果,挑选出key为EnableAutoConfiguration
的部分,即需要自动配置的部分,将其返回; -
getAutoConfigurationEntry()
获得META-INF/spring.factories
需要自动配置的部分,在根据@OnXXXCondition
注解筛选排除部分不生效的自动配置,将其返回给selectImports()
方法; -
点进去Map中的
EnableAutoConfiguration
,可以看到如下全为"xxxxAutoConfiguration"格式的字符串,这就是selectImports()
方法返回的需要添加到Spring容器中管理的组件的全限定类名,作用类似于使用@Import
引入了@Configuration
注解的类,通过这种方式实现自动配置。
-
2. 为什么有的自动配置没有生效/为什么有的配置被过滤掉:
在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。@Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
常见的Conditional注解:
-
@ConditionalOnClass
: classpath中存在该类时起效 -
@ConditionalOnMissingClass
: classpath中不存在该类时起效 -
@ConditionalOnBean
: DI容器中存在该类型Bean时起效 -
@ConditionalOnMissingBean
: DI容器中不存在该类型Bean时起效 -
@ConditionalOnSingleCandidate
: DI容器中该类型Bean只有一个或@Primary的只有一个时起效 -
@ConditionalOnExpression
: SpEL表达式结果为true时 -
@ConditionalOnProperty
: 参数设置或者值一致时起效 -
@ConditionalOnResource
: 指定的文件存在时起效 -
@ConditionalOnJndi
: 指定的JNDI存在时起效 -
@ConditionalOnJava
: 指定的Java版本存在时起效 -
@ConditionalOnWebApplication
: Web应用环境下起效 -
@ConditionalOnNotWebApplication
: 非Web应用环境下起效