【Spring】简述@Configuration配置类注册BeanDefinition到Spring容器的过程
概述
本文以SpringBoot应用为基础,尝试分析基于注解
@Configuration的配置类是如何向Spring容器注册BeanDefinition的过程
其中主要分析了
ConfigurationClassPostProcessor这个
BeanDefinitionRegistryPostProcessor即Bean定义注册后置处理器,在Spring启动过程中对@Configuration配置类的处理,主要体现在 解析并发现所有配置类,处理配置类的相关逻辑(如配置类上的@ComponentScan、@Import、@Bean注解等),注册其中的BeanDefinition
SpringBoot版本:2.0.9.RELEASE
Spring版本:5.0.13.RELEASE
ConfigurationClassPostProcessor如何被引入
首先看一下
ConfigurationClassPostProcessor的类继承关系
从红框中可以看出
ConfigurationClassPostProcessor是
BeanDefinitionRegistryPostProcessor接口的实现类,即是一个Bean定义注册的后置处理器,会在Spring容器启动时被调用,具体时机为
// 调用链 AbstractApplicationContext.refresh() => invokeBeanFactoryPostProcessors() => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
invokeBeanFactoryPostProcessors()会先调用所有的
BeanDefinitionRegistryPostProcessor之后,再调用所有的
BeanFactoryPostProcessor
ConfigurationClassPostProcessor又是如何被引入Spring的呢??
SpringBoot应用会在ApplicationContext应用上下文被创建的构造函数中
new AnnotatedBeanDefinitionReader这个用于注册基于注解的BeanDefinition的Reader,在其构造中又会调用
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)使用工具类向Spring容器中注册一些所谓的注解配置处理器,其中就包含
ConfigurationClassPostProcessor
// ConfigurationClassPostProcessor被注册 AnnotationConfigServletWebServerApplicationContext构造 => new AnnotatedBeanDefinitionReader(registry) => AnnotationConfigUtils.registerAnnotationConfigProcessors(registry) => 注册ConfigurationClassPostProcessor到Spring容器
ConfigurationClassPostProcessor处理过程简述
首先,ConfigurationClassPostProcessor后置处理器的处理入口为
postProcessBeanDefinitionRegistry()方法。其主要使用了
ConfigurationClassParser配置类解析器解析
@Configuration配置类上的诸如
@ComponentScan、
@Import、
@Bean等注解,并尝试发现所有的配置类;还使用了
ConfigurationClassBeanDefinitionReader注册所发现的所有配置类中的所有Bean定义;结束执行的条件是所有配置类都被发现和处理,相应的bean定义注册到容器
大致流程如下:
1、通过BeanDefinitionRegistry查找当前Spring容器中所有BeanDefinition
2、通过
ConfigurationClassUtils.checkConfigurationClassCandidate()检查BeanDefinition是否为 “完全配置类” 或 “简化配置类”,并对配置类做标记,放入集合待后续处理
Spring配置类的分类可以 参考
3、通过
ConfigurationClassParser解析器parse解析配置类集合,尝试通过它们找到其它配置类
4、使用
ConfigurationClassBeanDefinitionReader注册通过所发现的配置类中找到的所有beanDefinition
5、处理完一轮配置类后,查看BeanDefinitionRegistry中是否存在新加载的且还未被处理过的 “完全配置类” 或 “简化配置类”,有的话继续上面步骤
其中第3、4步后面重点分析
ConfigurationClassParser#parse():解析构建配置类
对于SpringBoot应用来说,参与解析的种子配置文件即为SpringBoot的Application启动类
解析构建配置类流程
通过ConfigurationClassParser解析器parse解析配置类集合,尝试通过它们找到其它配置类
循环解析所有配置类 ConfigurationClassParser#processConfigurationClass()
根据@Conditional的ConfigurationPhase.PARSE_CONFIGURATION阶段条件判断是否跳过配置类
注意:有些@Conditional是在当前这个PARSE_CONFIGURATION解析配置阶段使用的,有些是在REGISTER_BEAN注册beanDefinition阶段使用的
【重点】调用ConfigurationClassParser#doProcessConfigurationClass()循环解析配置类,直到不存在未处理过的父类
1、处理配置类的成员内部类: 检查其是否为“完全/简化配置类”,是则对其继续分析处理并将其放入分析器的属性configurationClasses- 2、处理@PropertySource: 将找到的PropertySource添加到environment的PropertySource集合
- 3、处理@ComponentScan: 扫描到的@Component类BeanDefinition就直接注册到Spring容器;如果组件为配置类,继续分析处理并将其放入分析器的属性configurationClasses
- 4、处理@Import: (1)处理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports();其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器
- (2)处理ImportBeanDefinitionRegistrar: 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义
- (3)其它Import:调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器
processDeferredImportSelectors():处理推迟的ImportSelector集合,其实就是延迟调用了processImports()
SpringBoot的自动配置类就是被DeferredImportSelector推迟导入的
解析构建配置类源码分析
ConfigurationClassParser#processConfigurationClass()
包含了处理单个配置类的大体流程,先根据
ConfigurationPhase.PARSE_CONFIGURATION解析配置阶段的
@Conditional条件判断当前配置类是否应该解析,之后调用
ConfigurationClassParser#doProcessConfigurationClass()循环解析配置类,直到不存在未处理过的父类
/** * 解析单个配置类 * 解析的最后会将当前配置类放到configurationClasses */ protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { /** * 根据@Conditional条件判断是否跳过配置类 * 注意:当前这个PARSE_CONFIGURATION解析配置阶段只会使用这个阶段的@Conditional条件,有些REGISTER_BEAN注册beanDefinition阶段的条件不会在此时使用 */ if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); // 如果configClass在已经分析处理的配置类记录中已存在 if (existingClass != null) { //如果配置类是被@Import注册的,return if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } // 否则,清除老的记录,在来一遍 else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. /** * 递归处理配置类及其超类层次结构 * 从当前配置类configClass开始向上沿着类继承结构逐层执行doProcessConfigurationClass,直到遇到的父类是由Java提供的类结束循环 */ SourceClass sourceClass = asSourceClass(configClass); /** * 循环处理配置类configClass直到sourceClass变为null,即父类为null * doProcessConfigurationClass的返回值是其参数configClass的父类 * 如果该父类是由Java提供的类或者已经处理过,返回null */ do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }
ConfigurationClassParser#doProcessConfigurationClass():真正解析配置类
通过解析配置类上的注解、内部成员类和方法构建一个完整的ConfigurationClass配置类,过程中如果发现了新的配置类可以重复调用此方法
真正解析过程中会处理
成员内部类、
@PropertySource、
@ComponentScan、
@Import、
@ImportSource、
@Bean方法等,流程如下:
- 1、处理配置类的成员内部类: 检查其是否为“完全/简化配置类”,是则对其继续分析处理并将其放入分析器的属性configurationClasses
- 2、处理@PropertySource: 将找到的PropertySource添加到environment的PropertySource集合
- 3、处理@ComponentScan: 扫描到的@Component类BeanDefinition就直接注册到Spring容器;如果组件为配置类,继续分析处理并将其放入分析器的属性configurationClasses
- 4、处理@Import: (1)处理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports();其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器
- (2)处理ImportBeanDefinitionRegistrar: 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义
- (3)其它Import: 调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器
/** * Apply processing and build a complete {@link ConfigurationClass} by reading the * annotations, members and methods from the source class. This method can be called * multiple times as relevant sources are discovered. * @param configClass the configuration class being build * @param sourceClass a source class * @return the superclass, or {@code null} if none found or previously processed */ @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // Recursively process any member (nested) classes first /** * 1、处理配置类的成员类(配置类内嵌套定义的类) * 内部嵌套类也可能是配置类,遍历这些成员类,检查是否为"完全/简化配置类" * 有的话,调用processConfigurationClass()处理它们,最终将配置类放入configurationClasses集合 */ processMemberClasses(configClass, sourceClass); // Process any @PropertySource annotations /** * 2、处理 @PropertySource * 将找到的PropertySource添加到environment的PropertySource集合 */ for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations /** * 3、处理 @ComponentScan * 处理用户手工添加的@ComponentScan,SpringBoot创建ApplicationContext时的ClassPathBeanDefinitionScanner是为了扫描启动类下的包 * 为的是找到满足条件的@ComponentScan,即@Component相关的组件,先扫描一下,扫描到的就注册为BeanDefinition * 看其中是否还有配置类,有的话parse()继续分析处理,配置类添加到configurationClasses集合 */ Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); // 如果当前配置类上有@ComponentScan,且使用REGISTER_BEAN注册beanDefinition的条件判断也不跳过的话 if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately // 立即扫描,扫描到的就注册为BeanDefinition,并获得扫描到的所有beanDefinition // 在处理SpringBoot启动类上的@ComponentScan时,虽然指指定了excludeFilters,但会根据启动类所在包推测basePackage,就会扫描到SpringBoot启动类包以下的Bean并注册 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed // 检查扫描到的beanDefinition中是否有配置类,有的话parse()继续分析处理,,配置类添加到configurationClasses集合 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations /** * 4、处理 @Import * (1)处理ImportSelector * 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports() * 其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器 * (2)处理ImportBeanDefinitionRegistrar * 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义 * (3)其它 * 调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器 */ processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations /** * 5、处理 @ImportResource * 添加到配置类的importedResources集合,后续loadBeanDefinitions()加载bean定义时再让这些导入BeanDefinitionReader自行读取bean定义 */ AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods /** * 6、处理个别@Bean方法 * 获取所有@Bean方法,并添加到配置类的beanMethods集合 */ Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces /** * 7、处理配置类接口上的default methods */ processInterfaces(configClass, sourceClass); // Process superclass, if any /** * 8、检查父类是否需要处理,如果父类需要处理返回父类,否则返回null * 如果存在父类,且不在knownSuperclasses已经分析过的父类列表里,返回并继续分析 */ if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions():读取配置类,基于配置信息注册BeanDefinition
读取配置类,基于配置信息注册BeanDefinition流程
在上面解析配置类的过程中,除了构建了一个完整的ConfigurationClass配置类,其实已经向
BeanDefinitionRegistry中添加了一些beanDefinition了,比如在处理
@ComponentScan时,扫描到的
@Component相关组件就已经注册了
而
ConfigurationClassBeanDefinitionReader会继续读取已经构建好的ConfigurationClass配置类中的成员变量,从而注册beanDefinition
构建好的ConfigurationClass配置类中在本阶段可用的成员变量包括:
Set<BeanMethod> beanMethods
: @Bean的方法Map<String, Class<? extends BeanDefinitionReader>> importedResources
:配置类上@ImportResource注解的类存入此集合,会使用BeanDefinitionReader读取Resource中的BeanDefinition并注册Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars
:ImportBeanDefinitionRegistrar集合
通过构建好的配置类的配置信息,使用ConfigurationClassBeanDefinitionReader注册所有能够读取到的beanDefinition:
根据
ConfigurationPhase.REGISTER_BEAN阶段
条件判断配置类是否需要跳过循环判断配置类以及导入配置类的类,使用ConfigurationPhase.REGISTER_BEAN阶段条件判断是否需要跳过只要配置类或导入配置类的类需要跳过即返回跳过
如果configClass.isImported(),将配置类自身注册为beanDefinition
注册配置类所有
@Bean方法
为beanDefinition注册由
@ImportedResources
来的beanDefinition,即通过其它类型Resource的BeanDefinitionReader
读取BeanDefinition并注册,如xml格式的配置源XmlBeanDefinitionReader
注册由
ImportBeanDefinitionRegistrars
来的beanDefinition
读取配置类,基于配置信息注册BeanDefinition源码分析
/** * Read a particular {@link ConfigurationClass}, registering bean definitions * for the class itself and all of its {@link Bean} methods. * 读取特定配置类,根据配置信息注册bean definitions */ private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { /** * 根据ConfigurationPhase.REGISTER_BEAN阶段条件判断配置类是否需要跳过 * 循环判断配置类以及导入配置类的类,使用ConfigurationPhase.REGISTER_BEAN阶段条件判断是否需要跳过 * 只要配置类或导入配置类的类需要跳过即返回跳过 */ if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } // 1、如果当前配置类是通过内部类导入 或 @Import导入,将配置类自身注册为beanDefinition if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } // 2、注册配置类所有@Bean方法为beanDefinition for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } // 3、注册由@ImportedResources来的beanDefinition // 即通过其它类型Resource的BeanDefinitionReader读取BeanDefinition并注册 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // 4、注册由ImportBeanDefinitionRegistrars来的beanDefinition loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
思维导图
请放大观看
参考
Spring BeanDefinitionRegistryPostProcessor:ConfigurationClassPostProcessor
- spring技术内幕笔记:IoC容器的初始化过程(3)- BeanDefinition的注册
- Spring源码分析--Ioc容器定位解析资源文件并注册BeanDefinition
- 手动向spring容器中注册BeanDefinition
- Spring源码分析----IOC容器的实现(IoC容器的初始化过程(定位、载入解析、注册))
- Spring4.3.x 容器中bean的创建过程(5)—— 注册bean的销毁方法
- Spring源码分析,BeanDefinition加载过程
- spring容器初始化,bean加载生成过程,源码解析学习
- 通过DefaultListableBeanFactory加载.xml配置文件学习Spring-IoC容器注册/加载bean的机制(源码走读)
- Spring中IOC容器的初始化过程
- 【Spring源码--IOC容器的实现】(四)BeanDefinition的注册
- Spring4.3.x 容器在web应用中的初始化过程
- Spring - Spring容器概念及其初始化过程
- 【Java.Spring.Core】【IoC】使用Classpath扫描向容器注册beans/components
- Spring IOC 加载过程(二 解析xml,注册bean)
- spring(2) spring容器的实现过程
- spring的启动过程——spring和springMVC父子容器的原理
- Spring核心容器的详细解析以及IOC和DI和过程全解
- Spring容器启动过程
- Spring源码 - 注册bean信息到ioc容器
- spring的启动过程——spring和springMVC父子容器的原理