SpringIOC BeanDefinition的加载流程详解
作者:AntBlack 发布时间:2023-11-25 18:06:22
一.前言
这一篇来看看 SpringIOC 里面的一个细节点 , 来简单看看 BeanDefinition 这个对象 , 以及有没有办法对其进行定制.
CASE 备份 : 👉 gitee.com/antblack/ca…
二. BeanDefinition 的体系
2.1 体系概览
这里面需要关注的几个类分别为 :
BeanDefinition 接口 : 顶层接口 , 抽象了Bean加载的方法
AbstractBeanDefinition : 提供了多数方法的默认实现
RootBeanDefinition : Spring BeanFactory 运行时统一的 BeanDefinition 视图
GenericBeanDefinition : 编程方式注册 BeanDefinition 的首选类
ChildBeanDefinition : 可继承BeanDefinition
下面来解释一下这里面说的一些概念 :
什么叫统一视图 ?
稍微从跟踪一下源码就能发现 , 从 xml 或者 JavaConfig 以及 Spring 默认加载的Bean配置类 ,最终都会被修饰为 RootBeanDefinition
GenericBeanDefinition 怎么用 ?
GenericBeanDefinition 是通过编程方式注入的 BeanDefinition 所对应的类 ,通常都是该类的子类 , 包括非Spring 的 ConfigBean 和 ServiceBean
ChildBeanDefinition 又做了什么 ?
一种可以继承 parent 配置的 BeanDefinition , 在加载环节中会通过 AbstractBeanFactory#getMergedLocalBeanDefinition() 来将 child 和 parent bean definition 进行合并。
BeanDefinition 进行 merge 操作时,会将 child 的属性与 parent 的属性进行合并,当有相同属性时,以 child 的为准
如果是 Map 形式的配置 , 会取并集
2.2 BeanDefinition 的作用
存储属性 : 基于接口 AttributeAccessor 实现
存储元数据配置 : 基于 BeanMetadataElement 实现
描述类的信息 : 包括Bean名称 , Primary 属性 , priority 配置 等等
Bean 的加载 : 例如 getBeansOfType , getBean 等等
总结其实就是一句话 : BeanDefinition 主要承载了Bean的元数据信息 ,同时描述了Bean在Spring体系中的加载方式 , 容器通过 BeanDefinition 中的配置来加载一个Bean
三. BeanDefinition 的载入
3.1 载入的入口
S1 : 启动配置类的载入
Spring 中第一个载入的 BeanDefinition 即为 RootBeanDefinition , 主要通过 AnnotationConfigUtils # registerAnnotationConfigProcessors 方法进行加载
在这个环节中会通过加载的方式分别载入多个不同的 RootBeanDefinition , 这里是 Contain 关系 :
// internalConfigurationAnnotationProcessor
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
// 构建对应的 PostProcessor 并且载入
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// internalAutowiredAnnotationProcessor
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
//.....
}
在这个环节中 , 基本上都是在通过 registerPostProcessor 来注册各类加载类 , 我把这些看成根类 .
这些类通常为 Spring 进行服务 , 用来配置各类信息和加载封装 Bean
S2 : 普通配置类的载入
普通类的载入包括 SpringApplication
和一些自定义的个人配置类 , 这些类主要为了对非 Spring 的组件进行注册 , 配置 , 注入等操作
这一类通常通过 registerBean 来实现Bean的注册 , 注册的入口也很多 :
包括 AnnotatedBeanDefinitionReader
和 ConfigurationClassPostProcessor
等, 不难发现这一类 BeanDefinition 通常都是由 RootBeanDefinition 装载的类进行载入的
通常注册出来的对象也为 AnnotatedGenericBeanDefinition 和 GenericBeanDefinition 的子类等
3.2 保存的逻辑
BeanDefinition 会在 DefaultListableBeanFactory # registerBeanDefinition
中进行注册.
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// S1 : 会对 BeanDefinition 进行校验 , 主要是MethodOverrides和FactoryMethodName不能同时存在
// -- 工厂方法必须创建具体的 Bean 实例 , 而 methodOverrides 会创建代理类且进行增强
// -- 也就是说 工厂需要实例 , 不能是代理类
if (beanDefinition instanceof AbstractBeanDefinition) {
((AbstractBeanDefinition) beanDefinition).validate();
}
// S2 : 判断 BeanDefinition 是否已经存在
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 是否允许同名Bean重写 , 因为此处已经存在一个了 , 不能重写则直接异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// 角色比较 ,只打日志
// ROLE_APPLICATION / ROLE_SUPPORT / ROLE_INFRASTRUCTURE
}else if (!beanDefinition.equals(existingDefinition)) {
// 判断是否为同一对象
}
// 以上主要是打log , 这里如果允许覆盖则直接覆盖了
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
// 判断该Bean是否已经开始初始化
if (hasBeanCreationStarted()) {
// 如果已经开始 , 需要对 Map 上锁后再处理
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
// 省略一些更新操作
}
}
else {
// 没有加载时的载入
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
// 如果Bean已经存在或已经开始加载了 , 这个时候时需要进行销毁操作的
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
protected void resetBeanDefinition(String beanName) {
// S1 : 如果已经创建 ,需要清空 Merge BeanDefinition
clearMergedBeanDefinition(beanName);
// S2 : 销毁 Bean
destroySingleton(beanName);
// S3 : 调用 PostProcessors 重置处理器进行处理
for (BeanPostProcessor processor : getBeanPostProcessors()) {
if (processor instanceof MergedBeanDefinitionPostProcessor) {
((MergedBeanDefinitionPostProcessor) processor).resetBeanDefinition(beanName);
}
}
// S4 : 如果该 BeanDefinition 是某个BeanDefinition 的Parent , 则需要同步处理
for (String bdName : this.beanDefinitionNames) {
if (!beanName.equals(bdName)) {
BeanDefinition bd = this.beanDefinitionMap.get(bdName);
if (bd != null && beanName.equals(bd.getParentName())) {
resetBeanDefinition(bdName);
}
}
}
}
3.3 使用的方式
BeanDefinition 的批量处理流程也是在 DefaultListableBeanFactory
中进行的
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 对所有的 BeanDefinition 进行循环处理
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 排除掉懒加载的Bean
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 此处省略工厂类的判断及处理 ...
// 进入Bean获取逻辑
getBean(beanName);
}
}
//.....
}
来源:https://juejin.cn/post/7157695463415087140
猜你喜欢
- Eureka注册的服务之间互相调用1.请求方启动类添加注解,扫描Eureka 中的全部服务@SpringBootApplication@En
- 一、Kotlin 调用 Java1. kotlin 关键字转义java 中的方法或变量 是 kotlin 的关键字时,使用反引号 `` 对关
- Java xml出现错误 javax.xml.transform.TransformerException: java.lang.NullP
- 本文基于SpringBoot 2.5.0-M2讲解Spring中Lifecycle和SmartLifecycle的作用和区别,以及如何控制S
- Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制
- 摘要:想必大家做开发的时候都会用到下拉刷新的控件,现在各种第三方的下拉刷新控件不胜枚举。当然最NB的还是XListView。其他也有针对Gr
- ★打印九九乘法表public class TestDemo { public static
- 对象创建的几种方法:使用new关键字使用clone方法反射机制反序列化以上四种都可以产生java对象1,3都会明确的显式的调用构造函数2是在
- ShardingSphereShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、
- 一、前言对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。二、MyBatis的初始化做了什么2.1 Mybatis的
- 1. 为什么要进行参数校验在后端进行工作时,需要接收前端传来的数据去数据库查询,但是如果有些数据过于离谱,我们就可以直接把它pass掉,不让
- 详解java 中Spring jsonp 跨域请求的实例jsonp介绍  
- 在类中自定义的“函数”称为“方法”,由于C#是完全面向对象的
- 1. 使用try-with-resources简化文件读取操作:修改前:FileInputStream fis = null;try { &
- 本文实例为大家分享了Android TextView实现跑马灯效果的具体代码,供大家参考,具体内容如下当Layout中只有一个TextVie
- 近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大
- 前言本文将实现一个MyBatis的Springboot的Starter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成My
- 目录环境准备1.数据库操作1.1获取所有数据库1.2获取指定库的所有集合名1.3.删除数据库2.文档操作2.1插入文档2.2查询文档2.3分
- 拆分实现流程请看下面这张图首先我们得对线程池进行一个功能拆分Thread Pool 就是我们的线程池,t1,t2,t3代表三个线程Block
- 1.ArrayList 是基数组结构的,需要连续的内存空间从构造函数可以看出,ArrayList内部用一个Object数组来保存数据。对于无