Spring源码系列(二):SpringBoot自动装配原理剖析

 2023-02-08
原文作者:_RichardLi 原文地址:https://juejin.cn/post/7083865841003364388

Spring源码系列(二):SpringBoot自动装配原理剖析

序言:

SpringBoot的自动装配在面试中经常被问。本文将先阐述一般的回答或者是说网上能找到的回答。

如果你想应付面试或急于寻找答案,请看第一部分 SpringBoot自动装配之面试回答 ,如果想要搞明白自动装配的原理,明白来龙去脉,不要忘记看第二部分 SpringBoot自动装配之原理剖析

SpringBoot自动装配之面试回答

首先我们先找到主启动类,如下图。

202301012031276721.png

然后点开@SpringBootApplication注解,会发现有很多注解,这里我们聚焦到这个@EnableAutoConfiguration注解,翻译过来意思就是 开启自动配置。

202301012031288552.png

然后我们继续点进去,会发现有个@Import注解,里面有个AutoConfigurationImportSelector的类。我们继续点开。

202301012031294153.png

并且找到里面两个非常重要的方法getAutoConfigurationEntrygetCandidateConfigurationsgetAutoConfigurationEntry方法名写的很清楚,获取自动装配的入口。然后里面有个getCandidateConfigurations方法,在里面的第一行就是SpringBoot完成自动装配的操作。 下面我们就这个方法来分析。

202301012031299774.png

202301012031307635.png

  1. loadFactoryNames()会传递两个参数,我们先点开第一个getSpringFactoriesLoaderFactoryClass(),其结果是返回EnableAutoConfiguration.class,第二个方法getBeanClassLoader()会返回一个加载bean的类加载器。
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
        				getBeanClassLoader());
  1. 然后我们点开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());
        	}
  1. 然后继续点击loadSpringFactories(),首先先从cache缓存中获取,如果缓存中已经有了配置文件,那么我们就直接返回结果(result),如果没有那么就通过url获取在线的配置文件。

    1. 这也就是我们在写xml文件中会发现最上面导入的dtd等文件,既然我们没有联网,我们的spring项目也能正常运行,因为我们本地缓存了这些文件,我们启动时不需要联网从本地缓存中获取即可。

    getResources()方法获取配置文件路径,FACTORIES_RESOURCE_LOCATION参数,它的值是META-INF/spring.factories

    202301012031313246.png

    202301012031320717.png

  2. 我们打开spring-boot-autoconfigure包下的spring.factories配置文件,还记得我们之前获取的完全限定名org.springframework.boot.autoconfigure.EnableAutoConfiguration嘛?

    我们通过这个完全限定名来加载这些类,然后将这些类名放进一个List集合中(configurations)。

    202301012031326998.png

  3. 最后,回到我们最开始的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的具体解析过程。

2023010120313496911.png

  1. 我们先进入getImports(sourceClass)方法,其中有两个Set集合,imports集合用来存储被@Import注解修饰的类,visited集合用来实现递归调用。以及主要的执行方法collectImports()

    2023010120313553412.png

  2. 我们进入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][]
  1. 现在已经执行完getImports(sourceClass)方法,获取到两个类:

    1. AutoConfigurationPackages中的内部类Registrar
    2. AutoConfigurationImportSelector
  2. 这个AutoConfigurationImportSelector就是实现自动装配的核心类,我们继续执行processImports()方法。

    2023010120313716514.png

    2023010120313861415.png 类图

    通过下图的代码注释,我们先遍历importCandidates集合,此集合里有两个值,就是步骤3获取到两个类。首先判断是否实现了ImportSelector接口,是否实现了DeferredImportSelector接口。不要着急,我们将用一张类图,清晰得展示这几个类之间的关系。

    根据类图可知,第一个AutoConfigurationPackages中的内部类Registrar没有实现ImportSelector接口,所以执行下面的逻辑,我们不在深究。

    deferred: adj. 延期的

    AutoConfigurationImportSelector实现ImportSelector,也实现了DeferredImportSelector接口,所以加入到deferredImportSelectorHandler中,延期处理。

  3. 然后@Imort注解解析已经完成,之后就解析@ImportResource、 @Bean等注解,和自动装配没有关系,忽略它。解析完这些标签,我们就要处理deferredImportSelectorHandler,点击process进入方法。然后再点击processorGroupImorts()

        // 处理deferredImportSelectorHandler的具体过程
        this.deferredImportSelectorHandler.process();
![2023010120313922416.png][] process方法

![2023010120314014117.png][] processGruopImports方法

6.点击getImports()方法中的process方法。执行getAutoConfigurationEntry后会返回一个AutoConfigurationEntry自动装配的入口对象。点进这个方法。我们会发现AutoConfigurationImportSelector中最重要的方法getCandidateConfigurations。然后下面的步骤,在上面的已经讲过了。到此我们实现了SpringBoot自动装配的过程。

2023010120314104818.png

2023010120314208719.png

总结

SringBoot其实并没有那么神奇,它的底层还有逻辑都是根据Spring来的,当我们明白Spring的执行过程后,不论是看SpringBoot、还是SpringMVC,SpringCloud,SpringSecurity都会得心应手。