SpringBoot第二特性:Starter启动依赖_02_SpringBoot处理配置类源码解析

Posted 毛奇志

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot第二特性:Starter启动依赖_02_SpringBoot处理配置类源码解析相关的知识,希望对你有一定的参考价值。

文章目录

一、前言

先回顾customizeservicestarter模块中spring.factories文件的内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry.customizeservicestarter.CustomizeConfiguration

从上述内容可以确定今天源码学习目标:

spring容器如何处理配置类;
spring boot配置类的加载情况;
spring.factories中的EnableAutoConfiguration配置何时被加载?
spring.factories中的EnableAutoConfiguration配置被加载后做了什么处理;

二、spring容器如何处理配置类?

  1. ConfigurationClassPostProcessor类的职责是处理配置类(金手指:配置类就是@Configuration标注的类);

Spring底层处理配置类的是ConfigurationClassPostProcessor类,这个ConfigurationClassPostProcessor类是在postProcessBeanDefinitionRegistry()方法中找到配置类的(这个postProcessBeanDefinitionRegistry()方法在spring启动的时候调用的),找到配置类之后,然后对所有配置类扫描注解parser.parse(candidates),包括 @PropertySource注解的处理逻辑、@ComponentScan注解的处理逻辑、@Import注解处理逻辑、@ImportResource注解处理逻辑、@Bean注解处理逻辑(@Configuration类里面有很多bean,就有@Bean注解
我们这里分析Spring对于@Import注解处理逻辑(为什么这里要查看@Import注解的处理逻辑,原因下面说),进入processImports(),这个方法查看candidate是否实现了ImportSelector接口,DeferredImportSelector接口,ImportBeanDefinitionRegistrar接口或都没实现(金手指:使用@Import注解的类一定要实现ImportSelector接口或DeferredImportSelector接口,源码中根据通过candidate isAssignable() 或 instanceof 来判断类是否实现了接口,从而判断类上面是否添加了@Import注解,这是一种间接的方式,要知道),完成相应的操作,
相应的什么操作?在哪里完成?
相应的什么操作?返回的名称实例化
在哪里完成?

  1. ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor接口的实现类,它的postProcessBeanDefinitionRegistry方法在容器初始化阶段会被调用(BeanDefinitionRegistryPostProcessor接口的更多细节请参考《spring4.1.8扩展实战之六:注册bean到spring容器(BeanDefinitionRegistryPostProcessor接口)》);

  2. ConfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法又调用processConfigBeanDefinitions方法处理具体业务;

  1. ConfigurationClassPostProcessor类中的processConfigBeanDefinitions方法中通过ConfigurationClassParser类来处理Configuration注解,如下图:

金手指:
candidates来自configCandidates,而configCandidates来自registry中注册的所有bean

  1. 如上图红框所示,所有被Configuration注解修饰过的类(金手指:就是在上面ConfigurationClassUtils.checkConfigurationClassCandidate()方法中判断为true,加入到configCandidates里面的),都会被parser.parse(candidates)处理,即ConfigurationClassParser类的parse方法;

  2. parse方法中调用processDeferredImportSelectors方法做处理:找到Configuration类中的Import注解(金手指:源码在ConfigurationClassParser类的processImports()方法),对于Import注解的值,如果实现了ImportSelector接口,就调用其selectImports方法,将返回的名称实例化:






private void processDeferredImportSelectors() 
		//这里就是Configuration注解中的Import注解的值,
		//例如EnableAutoConfiguration注解的源码中,Import注解的值是EnableAutoConfigurationImportSelector.class
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

		for (DeferredImportSelectorHolder deferredImport : deferredImports) 
			ConfigurationClass configClass = deferredImport.getConfigurationClass();
			try 
				//以EnableAutoConfiguration注解为例,其Import注解的值为EnableAutoConfigurationImportSelector.class,
				//那么此处就是在调用EnableAutoConfigurationImportSelector的selectImports方法,返回了一个字符串数组
				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
				//字符串数组中的每个字符串都代表一个类,此处做实例化
				processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
			
			catch (BeanDefinitionStoreException ex) 
				throw ex;
			
			catch (Throwable ex) 
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			
		
	

小结一下spring容器配置类的逻辑:

  1. 找出配置类;
  2. 找出配置类中的Import注解;
  3. Import注解的值是class,如果该class实现了ImportSelector接口,就调用其selectImports方法,将返回的名称实例化;

配置类@Configuration和@Import的关系是什么?

有了上面的结论就可以结合Spring boot的源码来分析加载了哪些数据了;

三、spring boot配置类的加载情况(Spring boot使用者是如何找到配置类并注册的)?

  1. 我们的应用使用了SpringBootApplication注解,看此注解的源码,使用了EnableAutoConfiguration注解:
@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 
	......
  1. EnableAutoConfiguration注解中,通过Import注解引入了EnableAutoConfigurationImportSelector.class:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 
	......
  1. 看EnableAutoConfigurationImportSelector的源码:
/**
 * @link DeferredImportSelector to handle @link EnableAutoConfiguration
 * auto-configuration. This class can also be subclassed if a custom variant of
 * @link EnableAutoConfiguration @EnableAutoConfiguration. is needed.
 *
 * @deprecated as of 1.5 in favor of @link AutoConfigurationImportSelector
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Madhura Bhave
 * @since 1.3.0
 * @see EnableAutoConfiguration
 */
@Deprecated
public class EnableAutoConfigurationImportSelector
		extends AutoConfigurationImportSelector 

	@Override
	protected boolean isEnabled(AnnotationMetadata metadata) 
		if (getClass().equals(EnableAutoConfigurationImportSelector.class)) 
			return getEnvironment().getProperty(
					EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
					true);
		
		return true;
	


上述源码有三处重点需要关注:

第一,EnableAutoConfigurationImportSelector是AutoConfigurationImportSelector的子类;

第二,EnableAutoConfigurationImportSelector已经被废弃了,不推荐使用;

第三,文档中已经写明废弃原因:从1.5版本开始,其特性由父类AutoConfigurationImportSelector实现;

  1. 查看AutoConfigurationImportSelector的源码,重点关注selectImports方法,该方法的返回值表明了哪些类会被实例化:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) 
		if (!isEnabled(annotationMetadata)) 
			return NO_IMPORTS;
		
		try 
		    //将所有spring-autoconfigure-metadata.properties文件中的键值对保存在autoConfigurationMetadata中
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			//取得所有配置类的名称
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		
		catch (IOException ex) 
			throw new IllegalStateException(ex);
		
	
  1. 通过上述代码可以发现,getCandidateConfigurations方法的调用是个关键,它返回的字符串都是即将被实例化的类名,来看此方法源码:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) 
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	
  1. getCandidateConfigurations方法中,调用了静态方法SpringFactoriesLoader.loadFactoryNames,上面提到的SpringFactoriesLoader.loadFactoryNames方法是关键,看看官方文档对此静态方法的描述,如下图红框所示,该方法会在spring.factories文件中寻找指定接口对应的实现类的全名(包名+实现类):

  1. 在getCandidateConfigurations方法中,调用SpringFactoriesLoader.loadFactoryNames的时候传入的指定类型是getSpringFactoriesLoaderFactoryClass方法的返回值:
protected Class<?> getSpringFactoriesLoaderFactoryClass() 
		return EnableAutoConfiguration.class;
	

现在可以梳理一下了:

spring boot应用启动时使用了EnableAutoConfiguration注解;

EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports方法返回的类名实例化后注册到spring容器;

EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自spring.factories文件内的配置信息,这些配置信息的key等于EnableAutoConfiguration;

现在真相大白了:只要我们在spring.factories文件内配置了EnableAutoConfiguration,那么对于的类就会被实例化后注册到spring容器;

至此,《自定义spring boot starter三部曲》系列就完结了,希望实战加源码分析的三篇文章,能帮助您理解和实现自定义starter这种简单快捷的扩展方式。

金手指:再次看一下selectImports()方法
找到AutoConfigurationImportSelector类中的selectImports()方法

定位getCandidateConfigurations()方法




金手指:ConfigurationClassPostProcessor类实现BeanDefinitionRegistryPostProcessor接口

ConfigurationClassPostProcessor类实现了BeanDefinitionRegistryPostProcessor接口,所以拥有了postProcessBeanDefinitionRegistry()方法,该方法在在容器初始化阶段会被调用时候调用;

金手指1:具体在AbstractApplicationContext抽象类的refresh()方法中的invokeBeanFactoryPostProcessors()方法里调用;
金手指2:该接口用于将bean注册到spring容器;
金手指3:BeanDefinitionRegistryPostProcessor接口是BeanFactoryPostProcessor的子接口,就是为什么ConfigurationClassPostProcessor类仅仅实现了BeanDefinitionRegistryPostProcessor接口,但是却同时拥有postProcessBeanDefinitionRegistry()方法和postProcessBeanFactory()方法的原因。
金手指4:两个方法的相同点是都调用了processConfigBeanDefinitions()方法。

同时,ConfigurationClassPostProcessor类实现了BeanFactoryPostProcessor接口,拥有了postProcessBeanFactory()方法,该方法在在容器初始化阶段会被调用时候调用;

金手指1:具体在AbstractApplicationContext抽象类的refresh()方法中的invokeBeanFactoryPostProcessors()方法里调用;
金手指2:该接口用于改变bean的定义,就是改变bean里面的属性,常用的是改变String类型变量值。

第二部分和第三部分是不同的

第二部分和第三部分是不同的,第二部分介绍spring底层如何处理配置类(对于那些使用了@Configuration的类,Spring是如何处理的),第三部分介绍实际的springboot的配置类(@Configuration注解标注,spring.factories中指定)如何使用注解进行加载的,如何作为SPI被使用者识别的,就是使用者去使用的时候,底层是如何通过提供方提供的spirng.factories文件找到提供方工程中的这个配置类的。

第二部分内容

第二部分:spring底层如何处理配置类(对于那些使用了@Configuration的类,Spring是如何处理的)

(1)找出配置类(ConfigurationClassPostProcessor类中的ConfigurationClassUtils.checkConfigurationClassCandidate()方法中判断为true,加入到configCandidates里面的);

(2)找出配置类中的Import注解(ConfigurationClassPostProcessor类中的parser.parse(candidates););

(3)Import注解的值是class,如果该class实现了ImportSelector接口,就调用其selectImports方法,将返回的名称实例化(ConfigurationClassParser类中的processImports()方法中的“if (candidate.isAssignable(ImportSelector.class)) 判断成功”);

第三部分内容:Spring boot使用者是如何找到配置类的

1、Spring boot使用者是如何找到配置类的?
AutoConfigurationImportSelector类的selectImports()方法的代码行getCandidateConfigurations(),取到所有配置类的名称,三个定位,META-INF/spring.factories key value

(1)spring boot应用上面使用SpringBootApplication注解,而SpringBootApplication注解上面使用了EnableAutoConfiguration注解;

重要金手指:Enable前缀注解上面一般有@Import注解,EnableAutoConfiguration注解也是这样,其他的,EnableDiscoveryClient注解,@EnableEurekaClient注解都是这样,@Import注解参数接收的类,就是实现了DeferredImportSelector接口或者ImportSelector接口的类,里面一定实现了selectImports()方法(因为这两个接口就是这个抽象方法),而且这个selectImports()方法用来返回类名数组。

@interface EnableAutoConfiguration  ```

(2)EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports方法返回的类名实例化后注册到spring容器;

将其selectImports方法返回的类名实例化后注册到spring容器;

(3)EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自spring.factories文件内的配置信息,这些配置信息的key等于EnableAutoConfiguration;

在springboot中,
key是EnableAutoConfiguration,value是配置类,所有这就是使用者去使用的时候,底层是如何通过提供方提供的spirng.factories文件找到提供方工程中的这个配置类的。
key和value就是通过这样确定的:

第二部分和第三部分共同点

第二部分和第三部分共同点是配置类

第二部分和第三部分的关系

第二部分是Spring底层如何处理配置类,是在ConfigurationClassPostProcessor中处理的,在spring启动的时候refresh()方法中处理的,找出配置类中的Import注解,如果Import注解的值是class,如果该class实现了ImportSelector接口,就调用其selectImports方法,将返回的名称实例化;

但是,第二部分只是调用selectImports()方法,具体的selectImports()方法的实现逻辑,在第三部分springboot项目上有一个。

第三部分是springboot是如何找到spi提供方的配置类的,具体的selectImports()的实现逻辑,返回需要实例化的类名称的数组。

实际运行中的执行顺序:启动的时候,springboot找到配置类
然后,找到配置类之后,spring ioc容器,即AbstractApplicationContext调用refresh()处理配置类,并处理上面的各个注解,其中包括@Import注解的处理,就是调用拥有该注解的类(都是ImportSelector接口,DeferredImportSelector接口或ImportBeanDefinitionRegistrar接口实现类,一定有selectImports()方法)的selectImports()方法,得到需要实例化的类名称的数组,并实例化。

四、小结

完成了,天天打码,天天进步!!

以上是关于SpringBoot第二特性:Starter启动依赖_02_SpringBoot处理配置类源码解析的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot启动机制(starter机制)核心原理详解

SpringBoot_03_依赖管理特性

Clickhouse 特性和优势

Clickhouse 特性和优势

SpringBoot 学习笔记心得自定义Starter启动器

springboot笔记七自定义场景启动器starter