Spring源码系列(二):SpringBoot自动装配原理剖析
序言:
SpringBoot的自动装配在面试中经常被问。本文将先阐述一般的回答或者是说网上能找到的回答。
如果你想应付面试或急于寻找答案,请看第一部分 SpringBoot自动装配之面试回答 ,如果想要搞明白自动装配的原理,明白来龙去脉,不要忘记看第二部分 SpringBoot自动装配之原理剖析 。
SpringBoot自动装配之面试回答
首先我们先找到主启动类,如下图。
然后点开@SpringBootApplication
注解,会发现有很多注解,这里我们聚焦到这个@EnableAutoConfiguration
注解,翻译过来意思就是 开启自动配置。
然后我们继续点进去,会发现有个@Import
注解,里面有个AutoConfigurationImportSelector
的类。我们继续点开。
并且找到里面两个非常重要的方法getAutoConfigurationEntry
和getCandidateConfigurations
。 getAutoConfigurationEntry
方法名写的很清楚,获取自动装配的入口。然后里面有个getCandidateConfigurations
方法,在里面的第一行就是SpringBoot完成自动装配的操作。 下面我们就这个方法来分析。
loadFactoryNames()
会传递两个参数,我们先点开第一个getSpringFactoriesLoaderFactoryClass()
,其结果是返回EnableAutoConfiguration.class
,第二个方法getBeanClassLoader()
会返回一个加载bean的类加载器。
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
- 然后我们点开
loadFactoryNames()
方法,发现我们从第一个方法中获取的EnableAutoConfiguration.class
以参数的形式传递给了factoryType
,然后我们通过getName()
方法获取了这个类的完全限定名名称:org.springframework.boot.autoconfigure.EnableAutoConfiguration
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
-
然后继续点击
loadSpringFactories()
,首先先从cache缓存中获取,如果缓存中已经有了配置文件,那么我们就直接返回结果(result),如果没有那么就通过url获取在线的配置文件。- 这也就是我们在写xml文件中会发现最上面导入的
dtd
等文件,既然我们没有联网,我们的spring项目也能正常运行,因为我们本地缓存了这些文件,我们启动时不需要联网从本地缓存中获取即可。
getResources()
方法获取配置文件路径,FACTORIES_RESOURCE_LOCATION
参数,它的值是META-INF/spring.factories
- 这也就是我们在写xml文件中会发现最上面导入的
-
我们打开
spring-boot-autoconfigure
包下的spring.factories
配置文件,还记得我们之前获取的完全限定名org.springframework.boot.autoconfigure.EnableAutoConfiguration
嘛?我们通过这个完全限定名来加载这些类,然后将这些类名放进一个List集合中(
configurations
)。 -
最后,回到我们最开始的
getAutoConfigurationEntry
方法,getCandidateConfigurations()
方法已经执行完,并且已经把spring.factories
中的配置文件放进了configurations
集合中,最后我们new一个AutoConfigurationEntry
对象,就完成了我们最初@Import
中的AutoConfigurationImportSelector
类,完成了自动装配
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
![202301012031334879.png][]
![2023010120313404510.png][]
SpringBoot自动装配之原理剖析
不管是Spring还是SpringBoot也好,在启动的时候都会初始化容器,其中一个最为重要的方法就是AbstractApplicationContext
中的refresh
方法。
当我们执行到refresh
方法中的invokeBeanFactoryPostProcessors(beanFactory)
时,会先执行其中的BeanDefinitionRegistry
中的postProcessBeanDefinitionRegistry
方法,其中有一个非常重要的类ConfigurationClassPostProcessor
。此类是一个后置处理器的类,主要功能是参与BeanFactory的建造,它是在执行refresh中的obtainFreshBeanFactory
方法解析<componment-scan />
标签的时候被加载进spring容器中的。(关于解析<componment-scan />
的具体过程可以看我的另一个文章juejin.cn/post/708130…)
ConfigurationClassPostProcessor的主要功能如下:
- 解析加了@Configuration的配置类
- 解析@ComponentScan扫描的包
- 解析@ComponentScans扫描的包
- 解析@Import注解
我们执行postProcessBeanDefinitionRegistry
方法时,里面会有一个parse.parse(candidates)
方法,此方法主要用来解析带有@Controller、@Import
、@ImportResource、@ComponentScan、@ComponentScans、@Bean的BeanDefinition。我们要实现自动装配肯定要自动导入一些类,那么自然需要用到@Import注解,来引入AutoConfigurationImportSelector
这个类。下面我们开始@Import
的具体解析过程。
-
我们先进入
getImports(sourceClass)
方法,其中有两个Set集合,imports
集合用来存储被@Import注解修饰的类,visited
集合用来实现递归调用。以及主要的执行方法collectImports()
-
我们进入
collectImports()
方法,发现里面还会执行collectImports()
方法,于是我们就发现,原来这里使用了递归。为什么要使用递归呢?
我们先看这个方法传入的参数sourceClass,SpringBoot的启动时运行一个主启动类,在我们第一次进入这个
getImports()
方法和collectImports()
方法时,参数里的sourceClass就是我们的主启动类,主启动类上有一个@SpringBootApplication
注解,里面的具体构造,我们再上面已经讲过了,就是里面有个@EnableAutoConfiguration
注解,里面有个@Import(AutoConfigurationImportSelector.class)
,我们导入这个类就能完成自动装配。但是你这个主启动类上的注解一层套一层,谁知道你里面加了几个
@Import
注解啊,所以我们需要递归。把所有的被@Import
注解修饰的类都找到,并加入到imports
集合中
// sourceClass.getAnnotations()获取到主启动类上的注解
for (SourceClass annotation : sourceClass.getAnnotations()) {
// annotation.getMetadata().getClassName() 先获取到注解的元信息,然后获取到类名称
String annName = annotation.getMetadata().getClassName();
// 如果说annName不是Import类型的,那就继续递归
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
// 最后加入到imports集合里
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
![2023010120313614013.png][]
-
现在已经执行完
getImports(sourceClass)
方法,获取到两个类:- AutoConfigurationPackages中的内部类Registrar
- AutoConfigurationImportSelector
-
这个
AutoConfigurationImportSelector
就是实现自动装配的核心类,我们继续执行processImports()
方法。类图
通过下图的代码注释,我们先遍历
importCandidates
集合,此集合里有两个值,就是步骤3获取到两个类。首先判断是否实现了ImportSelector
接口,是否实现了DeferredImportSelector
接口。不要着急,我们将用一张类图,清晰得展示这几个类之间的关系。根据类图可知,第一个
AutoConfigurationPackages
中的内部类Registrar
没有实现ImportSelector
接口,所以执行下面的逻辑,我们不在深究。deferred: adj. 延期的
而
AutoConfigurationImportSelector
实现ImportSelector
,也实现了DeferredImportSelector
接口,所以加入到deferredImportSelectorHandler
中,延期处理。 -
然后@
Imort
注解解析已经完成,之后就解析@ImportResource、 @Bean
等注解,和自动装配没有关系,忽略它。解析完这些标签,我们就要处理deferredImportSelectorHandler
,点击process进入方法。然后再点击processorGroupImorts()
// 处理deferredImportSelectorHandler的具体过程
this.deferredImportSelectorHandler.process();
![2023010120313922416.png][] process方法
![2023010120314014117.png][] processGruopImports方法
6.点击getImports()
方法中的process方法。执行getAutoConfigurationEntry
后会返回一个AutoConfigurationEntry
自动装配的入口对象。点进这个方法。我们会发现AutoConfigurationImportSelector
中最重要的方法getCandidateConfigurations
。然后下面的步骤,在上面的已经讲过了。到此我们实现了SpringBoot自动装配的过程。
总结
SringBoot其实并没有那么神奇,它的底层还有逻辑都是根据Spring来的,当我们明白Spring的执行过程后,不论是看SpringBoot、还是SpringMVC,SpringCloud,SpringSecurity都会得心应手。