编码之路

SpringBoot系列:(三)SpringBoot自动配置的原理

2019.11.24

SpringBoot的自动配置,主要靠类ConfigurationClassPostProcessor和ConfigurationClassParser来完成。

SpringBoot的自动装配流程总体为:向ApplicationContext中注册类ConfigurationClassPostProcessor;该类中,利用ConfigurationClassParser类来扫描启动类所属的包和子包中的组件,并将这些组件添加到Spring容器;利用ConfigurationClassParser扫描类路径下spring.factories文件中的EnableAutoConfiguration配置,并将这些自动配置类注册到Spring容器。

注册ConfigurationClassPostProcessor

不管何种类型的SpringBoot工程,在SpringApplication中生成的ConfigurableApplicationContext对象,不外乎以下三种:
(1)AnnotationConfigApplicationContext
(2)AnnotationConfigServletWebServerApplicationContext
(3)AnnotationConfigReactiveWebServerApplicationContext。

这三种类型的ApplicationContext构造方法中,都会生成一个AnnotatedBeanDefinitionReader对象。该AnnotatedBeanDefinitionReader对象在初始化时,会向Spring容器注册ConfigurationClassPostProcessor。

image.png

image.png

调用ConfigurationClassPostProcessor的processConfigBeanDefinitions方法

image.png

从ConfigurationClassPostProcessor的UML图可知,该类实现了BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor两个接口。

BeanFactoryPostProcessor的作用就不多做解释了,在上一章《 SpringBoot系列:(二)Spring容器是如何构建的》中有介绍。

public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanDefinitionRegistryPostProcessor接口是对BeanFactoryPostProcessor接口的扩展,用于在BeanFactoryPostProcessor发挥作用前,向容器添加bean定义信息。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

通过debug观察调用栈,结合上一章《 SpringBoot系列:(二)Spring容器是如何构建的》的内容,我们可以整理出如下的调用关系:
image.png

ConfigurationClassPostProcessor 中,不管是postProcessBeanDefinitionRegistry方法,还是postProcessBeanFactory方法,最核心的逻辑都在于调用processConfigBeanDefinitions方法。该方法大致逻辑如下:

(1)获取容器中未被解析的配置类。此时,该配置类一般为SpringBoot工程的启动类 。
(2)生成一个ConfigurationClassParser对象,用于对配置类的解析
(3)利用ConfigurationClassParser对象解析配置类。
	a. 扫描配置类所属的包和子包,获取到更多的配置类;
	b. 扫描spring.factories文件,读取EnableAutoConfiguration定义的配置类
	c. 将 a 和 b 获取的新配置类注册到容器
(4)从容器中获取已注册的配置类(排除掉已经解析过了的),然后用这些配置类重复步骤3。
    直到所有的配置类都已经解析完成。

image.png

ConfigurationClassParser的parse方法

我们从前面的代码走读中,可以看到ConfigurationClassPostProcessor其实内部利用ConfigurationClassParser来解析配置类的。而跟踪ConfigurationClassParser的parse方法,则发现最终调用了ConfigurationClassParser#processConfigurationClass方法

image.png

方法传入的ConfigurationClass对象是对配置类的封装。首先判断配置类上是否有@Conditional注解,是否需要跳过解析该配置类。

然后,调用doProcessConfigurationClass(configClass, sourceClass)做真正的解析。其中,configClass是程序的配置类,而sourceClass是通过configClass创建的。

image.png
image.png

ConfigurationClassParser 扫描配置类所在包和子包

从代码不难看出,ConfigurationClassParser 扫描配置类所在包和子包,是通过ComponentScanAnnotationParser来完成的。这里不做太细致分析,只说明其大致流程:通过配置类获取到配置类所属的包和子包,然后读取这些包中的组件;将满足条件的组件注册到容器。

ConfigurationClassParser 扫描spring.factories文件

我们再来看ConfigurationClassParser是如何读取spring.factories文件中的EnableAutoConfiguration配置项的吧。

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass){
	......
	// Process any @Import annotations
	processImports(configClass, sourceClass, getImports(sourceClass), true);
	......
}
						

image.png

从这段代码可以看到,如果配置类被@Import注解修饰,且该注解中对应的类是ImportSelector的子类的话,则会调用其ImportSelector#selectImports方法。该方法将新获得的类添加到容器中。

还记得SpringBoot工程启动类一般会被哪个注解修饰吗?当然是@SpringBootApplication注解啦!而这个注解通过继承的方式,会@Import(AutoConfigurationImportSelector.class)

而AutoConfigurationImportSelector恰恰是一个ImportSelector类的子类,因此其selectImports方法将会调用。

AutoConfigurationImportSelector#selectImports方法中,可以看到读取spring.factories文件EnableAutoConfiguration配置项的代码。

AutoConfigurationImportSelector#selectImports
AutoConfigurationImportSelector#getAutoConfigurationEntry
AutoConfigurationImportSelector#getCandidateConfigurations
AutoConfigurationImportSelector#getSpringFactoriesLoaderFactoryClass
注释:
(1)selectImports方法调用getAutoConfigurationEntry;
(2)getAutoConfigurationEntry调用getCandidateConfigurations
(3)getCandidateConfigurations调用getSpringFactoriesLoaderFactoryClass
(4)最终getSpringFactoriesLoaderFactoryClass返回EnableAutoConfiguration.class

image.png

image.png