首页 > 其他分享 >浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系

时间:2023-01-19 11:06:39浏览次数:47  
标签:BD 容器 浅谈 mbd Spring RootBeanDefintion BeanDefinitionMap Bean BeanDefinition


文章目录

  • ​​一、BeanDefinition​​
  • ​​1、具体实现子类​​
  • ​​2、手动创建子类​​
  • ​​3、beanClass成员变量的含义​​
  • ​​二、BeanDefinitionMap​​
  • ​​三、RootBeanDefintion​​
  • ​​1、具体获取的流程​​

通过本文你将收获:

  1. RootBeanDefintion、BeanDefinition、BeanDefinitionMap三者的关系即其基本含义
  2. BeanDefinition接口的实现子类
  3. BeanDefinition子类中beanClass成员变量的含义
  4. 根据beanName获取RootBeanDefintion的流程



一、BeanDefinition

BeanDefinition:Bean定义的接口,在Spring中该接口有许多实现类如下图。不同的BeanDefinition的实现类也代表着不同的含义。

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_字符串


1、具体实现子类

  1. 如果使用配置类的方式启动Spring容器,那么我们就会生成AnnotatedGenericBeanDefinition
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);



  1. 在实例化Bean之前,Spring需要先去加载自己的一些内部的Bean,比如@Configuration对应的Bean,此时使用的是RootBeanDefinition
  2. 浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_子类_02

  3. 当我们通过包路径去扫描Bean时,扫描出来的Bean定义使用的是ScannedGenericBeanDefinition
  4. 浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_字符串_03


观察上面的三种情况:

  1. 将配置类初始化为一个BeanDefinition的实现类
  2. 将@Configuration注解对应的类初始化为一个BeanDefinition的实现类
  3. 将扫描出来的符合要求Bean要求的类初始化为一个BeanDefinition的实现类


2、手动创建子类

同理,我们是不是也能添加BeanDefinition到Spring容器中?

于是你就可以看到如下的几步代码,该代码表示我们手动创建一个BeanDefinition的实现类,并将他添加到Spring容器中:

  1. 创建一个BeanDefinition
  2. 设置BeanDefinition的类型
  3. 将BeanDefinition添加到Spring容器中

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_java_04


3、beanClass成员变量的含义

点进AbstractBeanDefinition类的代码,我们会发现其beanClass的类型并非为Class类型,而是一个Object类型,按照前面Teacher类的描述,这个属性是用来标记对应Bean的类型的,难道直接使用Class类型不好吗?

@Nullable
private volatile Object beanClass;

其实beanClass这个属性最开始是一个String类型的类的全路径名字,后期Spring容器在初始化的时候会去校验这个属性是一个Class类型的类还是一个字符串,如果是字符串就通过将它解析为一个Class。


对应设置这个字段的位置在:

下面代码来自我们扫描包下符合要求的Bean时初始化的ScannedGenericBeanDefinition类的构造方法,给该方法传入类的元数据信息,对类元数据不熟悉的小伙伴可以参考:​​MetadataReader、ClassMetadata、AnnotationMetadata的简单使用​​

public ScannedGenericBeanDefinition(MetadataReader metadataReader) {
Assert.notNull(metadataReader, "MetadataReader must not be null");
this.metadata = metadataReader.getAnnotationMetadata();
// 获取类的全路径字符串,设置到beanClass属性上,此处设置的是字符串
setBeanClassName(this.metadata.getClassName());
setResource(metadataReader.getResource());
}


对应解析这个字段的位置在:

AbstractBeanFactory类中

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_字符串_05



AbstractAutowireCapableBeanFactory类中

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_子类_06



AbstractBeanFactory类中

  • 判断该属性是是否为一个类
  • 如果是一个类,表示该字段的值可以直接使用
  • 不是一个类,就去解析对应的字符串,通过解析为一个类

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_字符串_07

这也就是为什么我们直接设置为类也可以。


为了进一步验证我们的解释,我修改了我的测试代码:

我将对应的类设置为类的路径依然能够完成BeanDefinition的添加

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_java_08



需要测试成功还需要对类AbstractBeanDefinition进行一点小小的改动,因为原setBeanClass方法只能将参数设置为Class类型,我自己添加了一个设置为字符串属性的方法

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_spring_09



二、BeanDefinitionMap

看名字也知道是存放BeanDefinition的集合,此时的Bean还只是一个雏形,仅仅包含Bean的一些简单基本信息

类比理解单例池和Bean的关系。

前面铺垫了那么多的BeanDefinition的知识,就是后面你能更好的理解BeanDefinitionMap。

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);


当Spring扫描到了符合要求的类时,都会将类实例化为一个BeanDefinition,然后再将BeanDefinition添加到BeanDefinitionMap,即类似下面这样的代码(Spring中有很多地方都有这样的代码):

registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_mvc_10


调用registerBeanDefinition方法,参数为:Bean的名字,通过类实例化BeanDefinition的实现类(具体有哪些实现类上面已经进行了详细的说明)。

registry对象有三个实现,但其实GenericApplicationContext和DefaultListableBeanFactory最终会调用同一个方法,所以只会有两个不同的实现类SimpleBeanDefinitionRegistry和DefaultListableBeanFactory:

这个registry对象可以简单的理解为Spring容器,即表示我们将BeanDefinition注册到哪个容器(Spring容器有很多种实现)BeanDefinitionMap中。



三、RootBeanDefintion

这是BeanDefintion(后文再出现BeanDefintion以BD代替)众多实现中的一种

也是众多BD的归宿,无论你前面的BD是什么类型,在实例化Bean的时候,会去判断Bean的一些要素情况,此时会将所有的BD都转化为RootBD,继而完成接下来的操作

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_mvc_11


private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);

这个转化的逻辑就是去mergedBeanDefinitions集合中获取(类比理解BDMap,只是前者存的是BD接口,后者存的是BD的子类RootBD类)

  • 如果对应的mergedBeanDefinitions中有就直接返回
  • 如果没有就去BDMap中获取,然后转化为mergedBeanDefinitions中的数据

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_字符串_12



总结起来就是:Spring去扫描符合要求的类的时候,会将符合要求的类实例化为各种类型的BD(本文开头有说明),但是当我们去实例化所有非懒加载Bean的时候(完成Bean的生命周期),会将所有的BD类型进行统一,全部转化成RootBD,用于统一判断对应的BD信息


1、具体获取的流程

上面说到各种类型的BD转化为RootBD就是去对应的集合中获取,然后返回,但其实这个获取的过程还是比较复杂的。以下部分为单独对该过程进行的讲解。

在看代码逻辑之前,需要一些其他知识


补充知识点:

父子容器、父子Bean

在Spring中我们可以配置父子容器,用于加载两个容器中的Bean,SpringMVC中就利用了这个知识点。同样我们也可以配置父子Bean,代码如下

public class BianChengShiSpringTest2 {
public static void main(String[] args) {


AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext(ConfigBean2.class);
// 构建父子容器
context.setParent(context);

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(Teacher.class);

AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition2.setBeanClass(MoBian.class);
// 构建父子Bean
beanDefinition2.setParentName("teacher");

context.registerBeanDefinition("teacher",beanDefinition);
context.registerBeanDefinition("mobian",beanDefinition2);
System.out.println(context.getBean("teacher"));
System.out.println(context.getBean("mobian"));
}
}


获取的详细的注释已经写在了代码里面

protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {

synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;

// Check with full lock now in order to enforce the same merged instance.
if (containingBd == null) {
// 情况一
// 根据beanName去mergedBeanDefinitions集合中获取对象
mbd = this.mergedBeanDefinitions.get(beanName);
}

// 集合中没有,进入if(有直接返回)
if (mbd == null) {
// 判断BD是否存在父BD
if (bd.getParentName() == null) {
// Use copy of given root bean definition.
// 在没有父Bean(我们通常定义的Bean都是没有父Bean的)的情况下,如果bd是RootBD类型,就克隆一份属性
if (bd instanceof RootBeanDefinition) {
// 情况二
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
else {
// 情况三
// 如果不是RootBD,我们就将之前的BD属性传给RootBD,用于RootBD的数据初始化
mbd = new RootBeanDefinition(bd);
}
}
else {
// Child bean definition: needs to be merged with parent.
BeanDefinition pbd;
try {
// 进入此代码块,表示存在父Bean

// 此处代码与实现BeanFactory接口的Bean实现有关,除此之外都是传进去的beanName等于返回的beanName
// 有父Bean,就获取父Bean的名字
String parentBeanName = transformedBeanName(bd.getParentName());
if (!beanName.equals(parentBeanName)) {
// 情况四
// 如果父Bean的名字和自己不相同,就递归调用该方法,用于找到父Bean的数据
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
// 如果父Bean的名字和自己相同,就获取父Spring容器
// 同一个Spring容器中只能出现一个相同名字的Bean,所以该种情况出现时,父Bean只会存在于父容器中
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
// 情况五
// 去父容器中递归进行获取BD数据信息
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
// 除此之外就抛异常
else {
throw new NoSuchBeanDefinitionException(parentBeanName,
"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
"': cannot be resolved without a ConfigurableBeanFactory parent");
}
}
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
}
// Deep copy with overridden values.
// 将父BD的内容覆盖给子BD
mbd = new RootBeanDefinition(pbd);
// 将子BD原有的数据再覆盖一次。两步总结起来就是子BD没有的属性使用父类的,有的属性就使用自己原有的
mbd.overrideFrom(bd);
}

// Set default singleton scope, if not configured before.
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(SCOPE_SINGLETON);
}

// A bean contained in a non-singleton bean cannot be a singleton itself.
// Let's correct this on the fly here, since this might be the result of
// parent-child merging for the outer bean, in which case the original inner bean
// definition will not have inherited the merged outer bean's singleton status.
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}

// Cache the merged bean definition for the time being
// (it might still get re-merged later on in order to pick up metadata changes)
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}

return mbd;
}
}



核心步骤总结成下面的导图逻辑,供参考:

  • 一定要明白父子容器和父子Bean的概念
  • 同一个容器中是不能出现相同名字的Bean的(从本质出发,Spring容器的单例池是一个map,当map出现两个相同的key时,数据是会覆盖的,自然就有问题)

浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系_java_13


标签:BD,容器,浅谈,mbd,Spring,RootBeanDefintion,BeanDefinitionMap,Bean,BeanDefinition
From: https://blog.51cto.com/u_15942107/6019932

相关文章

  • 浅谈InnoDB存储引擎的MVCC机制
    文章目录​​一、数据库事务隔离级别​​​​1、事务原则​​​​2、4种隔离级别​​​​3、会出现的3种问题​​​​二、MVCC概念​​​​1、基本结构​​​​2、字段介绍......
  • 浅谈Spring中Bean的生命周期
    文章目录​​1、基本概念​​​​2、生命周期流程图​​​​3、源码与功能分析​​​​3.1、实例化前​​​​3.2、实例化​​​​3.3、实例化后​​​​3.4、初始化前前​......
  • 浅谈Netty中ServerBootstrap服务端源码(含bind全流程)
    文章目录​​一、梳理Java中NIO代码​​​​二、Netty服务端代码​​​​1、newNioEventLoopGroup()​​​​2、group​​​​3、channel​​​​4、NioServerSocketChanne......
  • 浅谈Redisson底层源码
    Redisson源码分析​​一、加锁时使用lua表达式,执行添加key并设置过期时间​​​​二、加锁成功之后给锁添加对应的事件​​​​三、加锁完成,看门狗自动续命未处理完的线程​......
  • 浅谈三种使用Redis实现MQ的方式
    文章目录​​一、消息队列​​​​二、基于List的消息队列​​​​三、基于PubSub的消息队列​​​​四、基于Stream的消息队列​​​​1、基本命令​​​​2、简单使用​​......
  • 浅谈如何使用Redis实现分布式锁
    文章目录​​一、基础版(含自动释放锁)​​​​二、改良版(含过期时间)​​​​三、进阶版(含唯一性验证)​​​​四、单节点版(含Redisson)​​​​五、多节点版(含RedLock)​​写在......
  • 浅谈Redis底层数据结构(sdshdr-redisObject)
    最近看了点Redis底层的源码分析,特作此记录前提共识:Redis是一个默认为16个数据库的key-value内存数据库Redis底层是由C语言实现文章目录​​C语言源码流程​​​​1、server.......
  • 浅谈Zookeeper集群选举Leader节点源码
    写在前面:zookeeper源码比较复杂,本文讲解的重点为各个zookeeper服务节点之间的state选举。至于各个节点之间的数据同步,不在文本的侧重讲解范围内。在没有对zookeeper组件有一......
  • 浅谈Redis基本数据类型底层编码(含C源码)
    文章目录​​一、String​​​​1、int​​​​2、embstr​​​​3、raw​​​​4、bitmap​​​​5、hyperloglog​​​​二、List​​​​1、ziplist​​​​2、quicklist......
  • 浅谈如何设计MySQL索引
    文章目录​​一、索引的代价​​​​二、如何设计索引​​​​1、索引列的类型尽量小​​​​2、索引的选择离散性高的​​​​3、只为用于搜索、排序或分组的列创建索引​......