@EnableAutoConfiguration的使用和原理

Springboot @EnableAutoConfiguration原理

  • Springboot是如何加载@EnableAutoConfiguration
    • 1. 如何把自己的配置类加入到Spring容器中呢?
      • 1.1 spring.factories
    • 2. 探究Springboot是如何加载我们的配置类的
      • 2.1 注解介绍
        • 2.1.1 @SpringbootApplication注解是什么
        • 2.1.2 @SpringbootConfiguration注解
        • 2.1.3 @EnableAutoConfiguration注解
      • 2.2 Springboot启动过程解析spring.factories
      • 2.3 Spring容器初始化过程中的处理@EnableAutoConfiguration注解
        • 2.3.1 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法执行过程
        • 2.3.2 ConfigurationClassParser#parse
        • 2.3.3 ConfigurationClassParser#processImports执行过程
        • 2.3.4 deferredImportSelectorHandler#process
        • 2.3.5 this.reader.loadBeanDefinitions(configClasses)
        • 2.3.6 补充一个方法调用时序图

Springboot是如何加载@EnableAutoConfiguration

本文着重介绍,如何让用户自定义的类通过@EnableAutoConfiguration加载

1. 如何把自己的配置类加入到Spring容器中呢?

1.1 spring.factories

此处代码,我们可以借助开源框架的mybatis-plus来理解
@EnableAutoConfiguration的使用和原理_第1张图片

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

可以发现此处直接把自己想要加入到spring容器中的配置类作为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value就行,是不是很简单。那么Springboot是如何基于这种配置加载我们的配置类的呢?

2. 探究Springboot是如何加载我们的配置类的

首先,让我们来看看Springboot的几个基础注解

2.1 注解介绍

2.1.1 @SpringbootApplication注解是什么
@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 {

可以看到该注解继承了@SpringbootConfiguration注解和@EnableAutoConfiguration注解

2.1.2 @SpringbootConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

此处可以看到@SpringbootConfiguration注解是@Configuration注解的子类

2.1.3 @EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

熟悉Springboot的@EnableXXX相关注解的同学,看到@Import注解就开心了~

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

不过需要注意的是此处AutoConfigurationImportSelector类实现的DeferredImportSelector而不同于一般的ImportSelector

通过对以上三个注解的,以及注解之间关系的介绍,大家可以把加了@SpringbootApplication注解的启动类认为是加了**@ComponentScan、@Configuration、@Import**注解的类

2.2 Springboot启动过程解析spring.factories

@EnableAutoConfiguration的使用和原理_第2张图片
Springboot在启动过程中,会在这个方法中,加载所有的spring.factories里面的配置,并缓存起来。

SpringFactoriesLoader类
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			//把classLoader作为key,将spring.factories里面的配置保存起来,cache是静态常量
			cache.put(classLoader, result);
			return result;
		}

@EnableAutoConfiguration的使用和原理_第3张图片
通过断点可以看到运行情况

2.3 Spring容器初始化过程中的处理@EnableAutoConfiguration注解

2.3.1 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法执行过程

此处涉及到Spring容器初始化过程,以后再记录。先简单记录下:
Spring容器初始化过程中,会调用ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法进行BeanDefinition的注册,此处简单记录下调用链:

postProcessBeanDefinitionRegistry -> 
processConfigBeanDefinitions -> 
ConfigurationClassParser#parse(这个方法牛逼) ->
ConfigurationClassParser#validate ->
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
2.3.2 ConfigurationClassParser#parse
ConfigurationClassParser#parse(这个方法牛逼) ->  {
	ConfigurationClassParser#processConfigurationClass -> {
		ConfigurationClassParser#doProcessConfigurationClass -> {
			1. 通过componentScanParser.parse来处理类上面的@ComponentScan和@ComponentScans
			2. processImports方法处理所有的@Import注解
			3. 处理@ImportResource注解
			4. 处理@Bean注解
		}
		this.configurationClasses.put(configClass, configClass);这一步很重要,很多开源框架都是通过调用此处,将类变为bd,注入的。
	}
	//注意这一步,也就是ConfigurationClassParser#parse方法的最后一步
	this.deferredImportSelectorHandler.process();
}
2.3.3 ConfigurationClassParser#processImports执行过程

今天重点关注processImports方法,拿到所有的@import注解里import进来的类:

1. 如果是DeferredImportSelector实现类:封装之后放入到ConfigurationClassParser.deferredImportSelectors中
2. 如果是ImportSelector实现类:调用接口的selectImports方法,对返回值的classNames进行processImports,递归循环起来
3. 如果是ImportBeanDefinitionRegistrar:实例化之后放入到ConfigurationClass.importBeanDefinitionRegistrars中,后续处理
4. 如果不满足上述三者,则对import进来的类进行ConfigurationClassParser#processConfigurationClass操作,这样就进入了循环递归,将import进来的配置类进行处理,同时在处理的过程中,会把配置类放入到ConfigurationClassParser.configurationClasses中,这一步很重要,很多开源框架都是通过调用此处,将类变为bd,注入的。
2.3.4 deferredImportSelectorHandler#process

由于上文介绍过AutoConfigurationImportSelector是DeferredImportSelector,所以放入到ConfigurationClassParser.deferredImportSelectors后续处理;

回到ConfigurationClassParser#parse中,当上述操作执行完了之后,进行

this.deferredImportSelectorHandler.process() -> {
	1. DeferredImportSelectorGroupingHandler#register -> {
		1.1. 调用了getImportGroup方法,获取class,生成对应的group。注意!!!AutoConfigurationImportSelector类重写了getImportGroup,返回了其内部类AutoConfigurationGroup
		}
	2. DeferredImportSelectorGroupingHandler#processGroupImports-> { 
		2.1. 调用group的process方法,
		也就是调用了AutoConfigurationGroup#process -> 
		getAutoConfigurationEntry -> 
		getCandidateConfigurations -> 
		SpringFactoriesLoader.loadFactoryNames 终于拿到了在2.2节放入到cache中的ClassName,拿到了以后你猜猜又干嘛了。对嘛,又调用了processImports方法。}
}
2.3.5 this.reader.loadBeanDefinitions(configClasses)

上述执行完成之后,ConfigurationClassPostProcessor#processConfigBeanDefinitions中,执行this.reader.loadBeanDefinitions(configClasses);此处会把ConfigurationClassParser.configurationClasses的keySet全都new成BeanDefinition放入到容器中,同时beanDefinitionNames中放入bd的名字,还要同时处理ImportResource注解内容,以及processImports的第3步的后续。

2.3.6 补充一个方法调用时序图

@EnableAutoConfiguration的使用和原理_第4张图片

之后就是Spring容器根据bd创建bean的过程了。

完~

你可能感兴趣的:(spring,springboot,源码分析,java,spring,spring,boot)