Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

一、

  在spring传统项目中,我们的bean定义信息是存放在xml中的,在项目启动的时候,需要将xml传递给容器

  但是到了springboot中,普遍使用javaConfig来定义bean,使用@Component、@Configuration、@ComponentScan这些基础注解实现的配置

  还有在自动配置中,一些按照约定注入的bean是写在spring.factories文件的,这些bean是通过@Import注解批量注册到容器中的

  但是在springboot启动的时候,我们是没有任何显式的操作,这些bean就会自动注册到容器中了,那么接下来就找找这个bean加载的源头。

 

二、

在开始之前,先复习一下,那些以@Component注解为基础的注解是需要配合@ComponentScan来扫描的,也就是说,我们至少需要知道扫描的范围

那么在springboot中,扫描的范围是在@SpringBootApplication --> @EnableAutoConfiguration --> @AutoConfigurationPackage --> @Import(AutoConfigurationPackages.Registrar.class) 确定的

注意到,方法参数中,已经可以确定包名了

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

然后通过注解的元信息,首先加入了basePackages、basePackageClasses的信息

然后如果这两个属性都没有值,也就是我们使用的是默认值,那么默认的packageNames就是这个注解标注的类所在的包,也就是我们的启动类

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

那么现在拿到了包的路径,也就是扫描的范围之后,就会将信息放在类中,无论bean定义信息中是不是已经存在,都会将这次的包信息加入到bean中

以beanName=AutoConfigurationPackages, 具体类是BasePackages.class的形式

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

三、

  在知道了我们扫描的范围之后,就可以开始找找到底是从哪里开始,我们自定义的bean以及那些自动配置的类,是什么时候被加载成beanDefinition的

  我们主要是找出ConfigurationClassPostProcessor这个类,是什么时候,在哪里加载的

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

这里一个题外话,sources是什么

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

其实是我们的启动类

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

这个看名字就很像了,加载beanDefinition的

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

扫描之后读取,在spring中也是这两种接口完成的功能

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

ConfigurationClassPostProcessor注册为beanName=internalConfigurationAnnotationProcessor

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

四、

  这样,对于配置类的后置处理器的加载,就找到了,接下来就可以看看这个类的执行时机,就可以知道我们自定义的bean啥时候加载了

  首先看看这个类的继承树

可以看到,BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor接口都有实现,不过从名字看出来,我们想看的是BeanDefinitionRegistryPostProcessor这个接口的实现

知道接口后,根据容器的生命周期,就知道这个类的执行时机了

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

是在容器刷新中的invokeBeanFactoryPostProcessors方法中被执行

在印象中,在这个方法之前的类,都是从spring.factories中直接读出来再反射生成,并没有通过beanFactory

在这个之后,我们就可以直接通过beanFactory拿到bean了

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

方法的第一段,将已经有的BeanDefinitionRegistryPostProcessor首先执行,顺便一提,这些最早存在的BeanDefinitionRegistryPostProcessor是通过spring.factories文件里面的initializer创建的

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

接下来就是将还没有实例化的BeanDefinitionRegistryPostProcessor实例化,会区分优先级去获取,然后排序,最后执行

之后按照这个套路对BeanFactoryPostProcessor接口再做一次

那么可以知道我们的ConfigurationClassPostProcessor是最高优先级,所以会最先执行postProcessBeanDefinitionRegistry方法, 然后是postProcessBeanFactory方法

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

知道了执行时机,接下來就可以查看这两个方法到底是如何为我们注册bean的

由于方法较长,一段段看

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

首先是将所有的beanDefinition拿出来,然后查看这些bean是不是有被标注了@Configuration

而且会检查是不是有重复处理的,如果没有被处理过,会被加入候选列表

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

如果没有被标注的类,就直接返回

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

排序

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

看看是不是有自定义的beanName生成规则,

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

然后开始解析bean,方法很长,先不进去具体的方法看,先从整体的逻辑去分析,这一大段代码做了什么

1、实例化一个 ConfigurationClassParser ,从名字猜测,就是专门解析我们的配置类的,然后是 parser.parse(candidates);

那我们知道,在一开始的时候,beanDefinition里面除了那些约定的类,也就只有我们自己的启动类了,而我们的启动类也是有@Configuration的,所以一开始其实就是解析我们的启动类

2、然后在解析启动类之后,将解析出来的所有类减去已经解析的类

3、实例化一个 ConfigurationClassBeanDefinitionReader ,随后 this.reader.loadBeanDefinitions(configClasses); 很明显,就是将我们解析出来的类注册成BeanDefinition ,然后去除已经解析的类

4、接着,清除了候选列表,判断现阶段注册的beanDefinition是不是比一开始拿出来的要多了,多了也就是证明这一个@Configuration配合@ComponentScan或者@Bean注册了许多bean

然后接下来就重复一开始的步骤,检查新注册的bean是不是还有@Configuration标注的,如果有就会继续加入候选列表

然后这个do...while循环一直到候选列表中没有可以解析的bean了,也就代表所有的@Configuration都被解析完成

5、最后注册了一个bean暂时不知道用处,注释看不懂,还有就是将元信息读取工厂的缓存清除了

然后整个解析的逻辑我们就已经清楚了,接下来重点就在那些bean是如何被扫描出来的,也就是bean的元信息和路径信息,有了这些才能给reader封装为beanDefinition以供后续实例化

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

分为三类解析,有注解的,有抽象的,有正常的,不过都是一个方法

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

递归检查是不是注解或者是不是@Controller等注解,然后进入真正的解析(这个递归检查暂时不太理解具体作用)

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

又是一个较长方法,不过此处已经接近核心解析代码,还是先看整体

1、解析@Component

2、解析@PropertySources

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

带有@ComponentScan注解的,会使用componentScanParser去解析,大概过程是将注解的元信息basePackage、过滤规则结合,然后将读取的类封装,具体的执行逻辑暂时不做阅读

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

1、处理@import注解

2、处理@importResource注解

3、处理带有@Bean注解的方法

4、处理接口中的默认方法

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

处理父类

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

五、

在上面的过程中,根据不同的注解会出现许多分支,那么在我们启动过程中,这个被解析的类就是我们的启动类

回去翻查启动类的注解中,具有的元信息是,有一个@ComponentScan定义了过滤规则,但是没有定义basePackage

但是如果看了@ComponentScan的处理逻辑中,会发现,如果标注的@ComponentScan没有定义basePackage,那么会将标注的类所在包作为basePackage

(具体的代码可以到 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 查看)

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

这时候就会出现一个疑问,因为启动类上还有一个@AutoConfigurationPackage,这个注解内部是@Import ,最终会注册一个 BasePackages.class ,里面也是带有以当前标注类的所在包为basePackage的信息

然后在查找该类是何时被使用的时候,发现它的使用场景多是数据库一侧,目前没有接触这一方面的源码,但是可以看到这个注解我们暂时没有使用

我们自定义的bean是通过@ComponentScan被扫描的

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

 

六、

  至此,对于beanDefinition的加载过程,做了一个简述,具体到每一个注解的具体分析,就留到以后再做下一层次的源码阅读。

如有错漏,欢迎指正。

   
上一篇:mockito 学习


下一篇:html静态页面实现微信分享思路