接上回,我们讲到了refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory)方法主要在执行BeanFactoryPostProcessor和其子接口BeanDefinitionRegistryPostProcessor的方法。

在创建AnnotationConfigApplicationContext对象时Spring就添加了一个非常重要的BeanFactoryPostProcessor接口实现类:ConfigurationClassPostProcessor。注意,这里说的添加只是添加到容器的beanDefinitionMap中,还没有创建真正的实例Bean。

简单回顾一下ConfigurationClassPostProcessor是在什么时候被添加到容器中的:在AnnotationConfigApplicationContext的无参构造器中创建AnnotatedBeanDefinitionReader对象时会向传入的BeanDefinitionRegistry中注册解析注解配置类相关的processors的BeanDefinitionConfigurationClassPostProcessor就是在此处被添加到容器中的。


ConfigurationClassPostProcessor

先看一些ConfigurationClassPostProcessor的继承体系:

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,也就拥有了在Spring容器启动时,往容器中注册BeanDefinition的能力。

我们知道,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法是在refresh();方法中的invokeBeanFactoryPostProcessors(beanFactory);中被执行的,下面我们就一起来看一下该方法。

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry);
}

主要的逻辑在processConfigBeanDefinitions(registry);中,点开源码:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//获取所有的BeanDefinitionName
String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName); // https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
// Full @Configuration vs “lite” @Bean mode
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
} // 校验是否为配置类
// 配置类分为两种 Full @Configuration vs “lite” @Bean mode
// 校验之后在 BeanDefinition 中添加标志属性
// 如果满足条件则加入到configCandidates
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 如果是配置类,就放到 configCandidates 变量中
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} // Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
} // Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
}); // Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
// 传入的 registry 是 DefaultListableBeanFactory
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
//获取自定义BeanNameGenerator,一般情况下为空
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
} if (this.environment == null) {
this.environment = new StandardEnvironment();
} // Parse each @Configuration class
// new ConfigurationClassParser,用来解析 @Configuration 类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 将 configCandidates 转成 set candidates , 去重
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解析配置类
parser.parse(candidates);
parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// Import类,@Bean,@ImportResource 转化为 BeanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses); candidates.clear();
// 再获取一下容器中BeanDefinition的数据,如果发现数量增加了,说明有新的BeanDefinition被注册了
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
} if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

获取所有的BeanDefinitionNames,然后循环这个数组,判断其是否为配置类。

前5个是Spring注册的内置processor,最后一个是传入给AnnotationConfigApplicationContext的配置类AppConfig.class

在Spring中存在两种ConfigurationClass,一种是FullConfigurationClass另一种是LiteConfigurationClass。关于这两者的区别,可以参看笔者文章之前关于Full @Configuration 和 lite @Bean mode的文章。

ConfigurationClassUtils#checkConfigurationClassCandidate方法内部就是在判断属于哪种配置类,并在BeanDefinition中标记判断结果。其具体的判断逻辑如下:

public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
return metadata.isAnnotated(Configuration.class.getName());
}
private static final Set<String> candidateIndicators = new HashSet<>(8); static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
} public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
} // Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
} // Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
} catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}

判断configCandidates变量中存放的

配置类是否为空,如果不为空,则对其进行排序。

创建ConfigurationClassParser对象,用于解析@Configuration类,完成包的扫描、BeanDefinition的注册。主要通过执行parser.parse(candidates);方法来完成。

执行parser.parse(candidates)方法前 :

执行parser.parse(candidates)方法后 :

解析完配置类之后,紧接着又执行了this.reader.loadBeanDefinitions(configClasses);方法。这个方法主要是用来处理Import类@Bean@ImportResource注解。关于这两个方法的具体细节,我们下次再讲。

最后又加了入了对ImportAware接口支持所需要的Bean。

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

关于对ImportAware接口的使用,我们也下次再讲。


未完待续......

源码学习笔记:https://github.com/shenjianeng/spring-code-study

欢迎各位关注公众号,大家一起学习成长。

Spring5源码解析5-ConfigurationClassPostProcessor (上)的更多相关文章

  1. Spring5源码解析-Spring框架中的单例和原型bean

    Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...

  2. Spring5源码解析-论Spring DispatcherServlet的生命周期

    Spring Web框架架构的主要部分是DispatcherServlet.也就是本文中重点介绍的对象. 在本文的第一部分中,我们将看到基于Spring的DispatcherServlet的主要概念: ...

  3. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

  4. Spring5源码解析_IOC之容器的基本实现

    前言: 在分析源码之前,我们简单回顾一下SPring核心功能的简单使用: 容器的基本用法 Bean是Spring最核心的东西,Spring就像是一个大水桶,而Bean就是水桶中的水,水桶脱离了水就没有 ...

  5. Spring5源码解析6-ConfigurationClassParser 解析配置类

    ConfigurationClassParser 在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configur ...

  6. Spring5源码解析2-register方法注册配置类

    接上回已经讲完了this()方法,现在来看register(annotatedClasses);方法. // new AnnotationConfigApplicationContext(AppCon ...

  7. Spring5源码解析1-从启动容器开始

    从启动容器开始 最简单的启动spring的代码如下: @Configuration @ComponentScan public class AppConfig { } public class Mai ...

  8. 一文带你解读Spring5源码解析 IOC之开启Bean的加载,以及FactoryBean和BeanFactory的区别。

    前言 通过往期的文章我们已经了解了Spring对XML配置文件的解析,将分析的信息组装成BeanDefinition,并将其保存到相应的BeanDefinitionRegistry中,至此Spring ...

  9. Spring5源码解析4-refresh方法之invokeBeanFactoryPostProcessors

    invokeBeanFactoryPostProcessors(beanFactory);方法源码如下: protected void invokeBeanFactoryPostProcessors( ...

随机推荐

  1. DELPHI GDI + TGPFont UnitPixel 问题解决

    查不少资料,在 GPfont := TGPFont.Create(fontFamily, Font.Size , FontStyleRegular,UnitPixel ); 时,显示的字体,并不是按D ...

  2. pip的使用

    目录 一.配置pip环境变量 二.Cmd终端使用pip 三.Pycharm使用pip 四.Jupyter使用pip 如果把python假想成一部手机,那么pip就是这部手机上的应用管家/APP,他可以 ...

  3. java8-Stream原理

    前言 java8新特性目前使用非常广泛,其中Stream更是最常用的特性,这篇文章将介绍Stream的原理,如果你现在还不怎么会用的话可以看一下菜鸟教 https://www.runoob.com/j ...

  4. 微信小程序集成腾讯云 IM SDK

    微信小程序集成腾讯云 IM SDK 1.背景 因业务功能需求需要接入IM(即时聊天)功能,一开始想到的是使用 WebSocket 来实现这个功能,然天意捉弄(哈哈)服务器版本太低不支持 wx 协议(也 ...

  5. .Net基础篇_学习笔记_第六天_类型转换和方法(函数)简介

    类型转换:Convert.ToInt32();  int.Parse();  int.TryParse(); Convert.ToInt32(); 的本质就是在使用 int.Parse(); int. ...

  6. linux下tomcat无法远程访问(开放8080端口)

    我们在linux下配置了tomcat后发现,无法访问除了linux(如果是虚拟机的话,宿主机子根本无法访问tomcat),解决下吧 原因是我们的tomcat访问需要8080端口,但是从外部访问,我们的 ...

  7. 由 [SDOI2012]Longge的问题 探讨欧拉函数和莫比乌斯函数的一些性质和关联

    本题题解 题目传送门:https://www.luogu.org/problem/P2303 给定一个整数\(n\),求 \[ \sum_{i=1}^n \gcd(n,i) \] 蒟蒻随便yy了一下搞 ...

  8. Java 自定义注解 校验指定字段对应数据库内容重复

    一.前言 在项目中,某些情景下我们需要验证编码是否重复,账号是否重复,身份证号是否重复等... 而像验证这类代码如下: 那么有没有办法可以解决这类似的重复代码量呢? 我们可以通过自定义注解校验的方式去 ...

  9. Linux初识之Centos7中terminal光标位置偏移问题的解决

    新安装的centos7打开terminal发现光标位置向右偏移,使用起来影响感官,经查询后找到类似情况并顺利解决问题,特记录解决过程以作参考. 1.未解决时光标向右偏移显示: 2.打开设置(Setti ...

  10. Unity-遇到的问题小总结

    1. event trigger后面显示不了对应的方法 原因:我是直接把脚本拖拽进去的,这是没有实例化的,拖拽进去的应该是挂载这个脚本的GameObject就可以了 2.制作prefeb 将场景中的单 ...