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容器如何处理配置类?
- 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注解,这是一种间接的方式,要知道),完成相应的操作,
相应的什么操作?在哪里完成?
相应的什么操作?返回的名称实例化
在哪里完成?
-
ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor接口的实现类,它的postProcessBeanDefinitionRegistry方法在容器初始化阶段会被调用(BeanDefinitionRegistryPostProcessor接口的更多细节请参考《spring4.1.8扩展实战之六:注册bean到spring容器(BeanDefinitionRegistryPostProcessor接口)》);
-
ConfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法又调用processConfigBeanDefinitions方法处理具体业务;
- ConfigurationClassPostProcessor类中的processConfigBeanDefinitions方法中通过ConfigurationClassParser类来处理Configuration注解,如下图:
金手指:
candidates来自configCandidates,而configCandidates来自registry中注册的所有bean
-
如上图红框所示,所有被Configuration注解修饰过的类(金手指:就是在上面ConfigurationClassUtils.checkConfigurationClassCandidate()方法中判断为true,加入到configCandidates里面的),都会被parser.parse(candidates)处理,即ConfigurationClassParser类的parse方法;
-
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容器配置类的逻辑:
- 找出配置类;
- 找出配置类中的Import注解;
- Import注解的值是class,如果该class实现了ImportSelector接口,就调用其selectImports方法,将返回的名称实例化;
配置类@Configuration和@Import的关系是什么?
有了上面的结论就可以结合Spring boot的源码来分析加载了哪些数据了;
三、spring boot配置类的加载情况(Spring boot使用者是如何找到配置类并注册的)?
- 我们的应用使用了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
......
- EnableAutoConfiguration注解中,通过Import注解引入了EnableAutoConfigurationImportSelector.class:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
......
- 看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实现;
- 查看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);
- 通过上述代码可以发现,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;
- getCandidateConfigurations方法中,调用了静态方法SpringFactoriesLoader.loadFactoryNames,上面提到的SpringFactoriesLoader.loadFactoryNames方法是关键,看看官方文档对此静态方法的描述,如下图红框所示,该方法会在spring.factories文件中寻找指定接口对应的实现类的全名(包名+实现类):
- 在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机制)核心原理详解