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

文章目录

通过本文你将收获:

  1. RootBeanDefintion、BeanDefinition、BeanDefinitionMap三者的关系即其基本含义

  2. BeanDefinition接口的实现子类

  3. BeanDefinition子类中beanClass成员变量的含义

  4. 根据beanName获取RootBeanDefintion的流程





一、BeanDefinition

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

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



1、具体实现子类

  1. 如果使用配置类的方式启动Spring容器,那么我们就会生成AnnotatedGenericBeanDefinition
    浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

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

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


观察上面的三种情况:

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




2、手动创建子类

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

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

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

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




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三者的关系


AbstractAutowireCapableBeanFactory类中

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


AbstractBeanFactory类中

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

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

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



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

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

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


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

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






二、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三者的关系

调用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三者的关系



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

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

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

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


总结起来就是: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三者的关系

上一篇:C++学习重点记录(Week4 & Week5)


下一篇:Spring源码 - Bean的依赖注入和查找