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) 确定的
注意到,方法参数中,已经可以确定包名了
然后通过注解的元信息,首先加入了basePackages、basePackageClasses的信息
然后如果这两个属性都没有值,也就是我们使用的是默认值,那么默认的packageNames就是这个注解标注的类所在的包,也就是我们的启动类
那么现在拿到了包的路径,也就是扫描的范围之后,就会将信息放在类中,无论bean定义信息中是不是已经存在,都会将这次的包信息加入到bean中
以beanName=AutoConfigurationPackages, 具体类是BasePackages.class的形式
三、
在知道了我们扫描的范围之后,就可以开始找找到底是从哪里开始,我们自定义的bean以及那些自动配置的类,是什么时候被加载成beanDefinition的
我们主要是找出ConfigurationClassPostProcessor这个类,是什么时候,在哪里加载的
这里一个题外话,sources是什么
其实是我们的启动类
这个看名字就很像了,加载beanDefinition的
扫描之后读取,在spring中也是这两种接口完成的功能
将ConfigurationClassPostProcessor注册为beanName=internalConfigurationAnnotationProcessor
四、
这样,对于配置类的后置处理器的加载,就找到了,接下来就可以看看这个类的执行时机,就可以知道我们自定义的bean啥时候加载了
首先看看这个类的继承树
可以看到,BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor接口都有实现,不过从名字看出来,我们想看的是BeanDefinitionRegistryPostProcessor这个接口的实现
知道接口后,根据容器的生命周期,就知道这个类的执行时机了
是在容器刷新中的invokeBeanFactoryPostProcessors方法中被执行
在印象中,在这个方法之前的类,都是从spring.factories中直接读出来再反射生成,并没有通过beanFactory
在这个之后,我们就可以直接通过beanFactory拿到bean了
方法的第一段,将已经有的BeanDefinitionRegistryPostProcessor首先执行,顺便一提,这些最早存在的BeanDefinitionRegistryPostProcessor是通过spring.factories文件里面的initializer创建的
接下来就是将还没有实例化的BeanDefinitionRegistryPostProcessor实例化,会区分优先级去获取,然后排序,最后执行
之后按照这个套路对BeanFactoryPostProcessor接口再做一次
那么可以知道我们的ConfigurationClassPostProcessor是最高优先级,所以会最先执行postProcessBeanDefinitionRegistry方法, 然后是postProcessBeanFactory方法
知道了执行时机,接下來就可以查看这两个方法到底是如何为我们注册bean的
由于方法较长,一段段看
首先是将所有的beanDefinition拿出来,然后查看这些bean是不是有被标注了@Configuration
而且会检查是不是有重复处理的,如果没有被处理过,会被加入候选列表
如果没有被标注的类,就直接返回
排序
看看是不是有自定义的beanName生成规则,
然后开始解析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以供后续实例化
分为三类解析,有注解的,有抽象的,有正常的,不过都是一个方法
递归检查是不是注解或者是不是@Controller等注解,然后进入真正的解析(这个递归检查暂时不太理解具体作用)
又是一个较长方法,不过此处已经接近核心解析代码,还是先看整体
1、解析@Component
2、解析@PropertySources
带有@ComponentScan注解的,会使用componentScanParser去解析,大概过程是将注解的元信息basePackage、过滤规则结合,然后将读取的类封装,具体的执行逻辑暂时不做阅读
1、处理@import注解
2、处理@importResource注解
3、处理带有@Bean注解的方法
4、处理接口中的默认方法
处理父类
五、
在上面的过程中,根据不同的注解会出现许多分支,那么在我们启动过程中,这个被解析的类就是我们的启动类
回去翻查启动类的注解中,具有的元信息是,有一个@ComponentScan定义了过滤规则,但是没有定义basePackage
但是如果看了@ComponentScan的处理逻辑中,会发现,如果标注的@ComponentScan没有定义basePackage,那么会将标注的类所在包作为basePackage
(具体的代码可以到 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 查看)
这时候就会出现一个疑问,因为启动类上还有一个@AutoConfigurationPackage,这个注解内部是@Import ,最终会注册一个 BasePackages.class ,里面也是带有以当前标注类的所在包为basePackage的信息
然后在查找该类是何时被使用的时候,发现它的使用场景多是数据库一侧,目前没有接触这一方面的源码,但是可以看到这个注解我们暂时没有使用
我们自定义的bean是通过@ComponentScan被扫描的
六、
至此,对于beanDefinition的加载过程,做了一个简述,具体到每一个注解的具体分析,就留到以后再做下一层次的源码阅读。
如有错漏,欢迎指正。
标签:springboot,Spring,ComponentScan,bean,源码,注解,解析,我们,beanDefinition 来源: https://www.cnblogs.com/huangwenhao1024/p/15068229.html