Spring官网阅读(五)BeanDefinition(下)

拼搏现实的明天。 2023-06-27 12:51 16阅读 0赞

上篇文章已经对BeanDefinition做了一系列的介绍,这篇文章我们开始学习BeanDefinition合并的一些知识,完善我们整个BeanDefinition的体系,Spring在创建一个bean时多次进行了BeanDefinition的合并,对这方面有所了解也是为以后阅读源码做准备。本文主要对应官网中的1.7小节

文章目录

        • 什么是合并?
          • 合并的总结:
          • 关于合并需要注意的点:
        • Spring在哪些阶段做了合并?
          • 1、扫描并获取到`bd`:
          • 2、实例化
        • 为什么需要合并?
        • 合并的代码分析:
        • 总结:

在 上篇文章中,我们学习了 BeanDefinition的一些属性,其中有以下几个属性:

  1. // 是否抽象
  2. boolean isAbstract();
  3. // 获取父BeanDefinition的名称
  4. String getParentName();

上篇文章中说过,这几个属性跟BeanDefinition的合并相关,那么我先考虑一个问题,什么是合并呢?

什么是合并?

我们来看官网上的一段介绍:
在这里插入图片描述
大概翻译如下:

一个BeanDefinition包含了很多的配置信息,包括构造参数,setter方法的参数还有容器特定的一些配置信息,比如初始化方法,静态工厂方法等等。一个子的BeanDefinition可以从它的父BeanDefinition继承配置信息,不仅如此,还可以覆盖其中的一些值或者添加一些自己需要的属性。使用BeanDefinition的父子定义可以减少很多的重复属性的设置,父BeanDefinition可以作为BeanDefinition定义的模板。

我们通过一个例子来观察下合并发生了什么,编写一个Demo如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  3. <bean id="parent" abstract="true" class="com.dmz.official.merge.TestBean">
  4. <property name="name" value="parent"/>
  5. <property name="age" value="1"/>
  6. </bean>
  7. <bean id="child" class="com.dmz.official.merge.DerivedTestBean" parent="parent" >
  8. <property name="name" value="override"/>
  9. </bean>
  10. </beans>
  11. public class DerivedTestBean {
  12. private String name;
  13. private int age;
  14. // 省略getter setter方法
  15. }
  16. public class TestBean {
  17. private String name;
  18. private String age;
  19. // 省略getter setter方法
  20. }
  21. public class Main {
  22. public static void main(String[] args) {
  23. ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml");
  24. DerivedTestBean derivedTestBean = (DerivedTestBean) cc.getBean("child");
  25. System.out.println("derivedTestBean的name = " + derivedTestBean.getName());
  26. System.out.println("derivedTestBean的age = " + derivedTestBean.getAge());
  27. }
  28. }

运行:


derivedTestBean的name = override
derivedTestBean的age = 1


在上面的例子中,我们将DerivedTestBeanparent属性设置为了parent,指向了我们的TestBean,同时将TestBean的age属性设置为1,但是我们在配置文件中并没有直接设置DerivedTestBean的age属性。但是在最后运行结果,我们可以发现,DerivedTestBean中的age属性已经有了值,并且为1,就是我们在其parent Bean(也就是TestBean)中设置的值。也就是说,BeanDefinition会从父BeanDefinition中继承没有的属性。另外,DerivedTestBeanTestBean都指定了name属性,但是可以发现,这个值并没有被覆盖掉,也就是说,BeanDefinition中已经存在的属性不会被父BeanDefinition中所覆盖

合并的总结:

所以我们可以总结如下:

  • BeanDefinition会从父BeanDefinition中继承没有的属性
  • 这个过程中,BeanDefinition中已经存在的属性不会被父BeanDefinition中所覆盖
关于合并需要注意的点:

另外我们需要注意的是:

  • BeanDefinition中的class属性如果为null,同时父BeanDefinition又指定了class属性,那么子BeanDefinition也会继承这个class属性。
  • BeanDefinition必须要兼容父BeanDefinition中的所有属性。这是什么意思呢?以我们上面的demo为例,我们在父BeanDefinition中指定了name跟age属性,但是如果子BeanDefinition中子提供了一个name的setter方法,这个时候Spring在启动的时候会报错。因为子BeanDefinition不能承接所有来自父BeanDefinition的属性
  • 关于BeanDefinitionabstract属性的说明:

    1. 并不是作为父BeanDefinition就一定要设置abstract属性为true,abstract只代表了这个BeanDefinition是否要被Spring进行实例化并被创建对应的Bean,如果为true,代表容器不需要去对其进行实例化。
    2. 如果一个BeanDefinition被当作父BeanDefinition使用,并且没有指定其class属性。那么必须要设置其abstract为true
    3. abstract=true一般会跟父BeanDefinition一起使用,因为当我们设置某个BeanDefinitionabstract=true时,一般都是要将其当作BeanDefinition的模板使用,否则这个BeanDefinition也没有意义,除非我们使用其它BeanDefinition来继承它的属性

Spring在哪些阶段做了合并?

下文将所有BeanDefinition简称为bd

1、扫描并获取到bd

这个阶段的操作主要发生在invokeBeanFactoryPostProcessors,对应方法的调用栈如下:

在这里插入图片描述

对应的执行该方法的类为:PostProcessorRegistrationDelegate

方法源码如下:

  1. public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,
  2. List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
  3. // .....
  4. // 省略部分代码,省略的代码主要时用来执行程序员手动调用API注册的容器的后置处理器
  5. // .....
  6. // 发生一次bd的合并
  7. // 这里只会获取实现了BeanDefinitionRegistryPostProcessor接口的Bean的名字
  8. String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  9. for (String ppName : postProcessorNames) {
  10. // 筛选实现了PriorityOrdered接口的后置处理器
  11. if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
  12. currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  13. // 去重
  14. processedBeans.add(ppName);
  15. }
  16. }
  17. // .....
  18. // 只存在一个internalConfigurationAnnotationProcessor 处理器,用于扫描
  19. // 这里只会执行了实现了PriorityOrdered跟BeanDefinitionRegistryPostProcessor的后置处理器
  20. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  21. // .....
  22. // 这里又进行了一个bd的合并
  23. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  24. for (String ppName : postProcessorNames) {
  25. // 筛选实现了Ordered接口的后置处理器
  26. if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
  27. currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  28. processedBeans.add(ppName);
  29. }
  30. }
  31. // .....
  32. // 执行的是实现了BeanDefinitionRegistryPostProcessor接口跟Ordered接口的后置处理器
  33. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  34. boolean reiterate = true;
  35. while (reiterate) {
  36. reiterate = false;
  37. // 这里再次进行了一次bd的合并
  38. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  39. for (String ppName : postProcessorNames) {
  40. if (!processedBeans.contains(ppName)) {
  41. // 筛选只实现了BeanDefinitionRegistryPostProcessor的后置处理器
  42. currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  43. processedBeans.add(ppName);
  44. reiterate = true;
  45. }
  46. }
  47. sortPostProcessors(currentRegistryProcessors, beanFactory);
  48. registryProcessors.addAll(currentRegistryProcessors);
  49. // 执行的是普通的后置处理器,即没有实现任何排序接口(PriorityOrdered或Ordered)
  50. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  51. currentRegistryProcessors.clear();
  52. }
  53. // .....
  54. // 省略部分代码,这部分代码跟BeanfactoryPostProcessor接口相关,这节bd的合并无关,下节容器的扩展点中我会介绍
  55. // .....
  56. }

在这里插入图片描述
大家可以结合我画的图跟上面的代码过一遍流程,只要弄清楚一点就行,即每次调用beanFactory.getBeanNamesForType都进行了一次bd的合并。getBeanNamesForType这个方法主要目的是为了或者指定类型的bd的名称,之后通过bd的名称去找到指定的bd,然后获取对应的Bean,比如上面方法三次获取的都是BeanDefinitionRegistryPostProcessor这个类型的bd

我们可以思考一个问题,为什么这一步需要合并呢?大家可以带着这个问题继续往下看,在后文我会解释。

2、实例化

Spring在实例化一个对象也会进行bd的合并。

第一次:

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

  1. public void preInstantiateSingletons() throws BeansException {
  2. // .....
  3. // 省略跟合并无关的代码
  4. for (String beanName : beanNames) {
  5. RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  6. if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
  7. // .....

第二次:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

  1. protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
  2. @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  3. // .....
  4. // 省略跟合并无关的代码
  5. final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
  6. checkMergedBeanDefinition(mbd, beanName, args);
  7. // Guarantee initialization of beans that the current bean depends on.
  8. String[] dependsOn = mbd.getDependsOn();
  9. if (dependsOn != null) {
  10. // ....
  11. }
  12. if (mbd.isSingleton()) {
  13. // ....
  14. }
  15. // ....

我们可以发现这两次合并有一个共同的特点,就是在合并之后立马利用了合并之后的bd我们简称为mbd做了一系列的判断,比如上面的dependsOn != nullmbd.isSingleton()。基于上面几个例子我们来分析:为什么需要合并?

为什么需要合并?

在扫描阶段,之所以发生了合并,是因为Spring需要拿到指定了实现了BeanDefinitionRegistryPostProcessor接口的bd的名称,也就是说,Spring需要用到bd的名称。所以进行了一次bd的合并。在实例化阶段,是因为Spring需要用到bd中的一系列属性做判断所以进行了一次合并。我们总结起来,其实就是一个原因:Spring需要用到bd的属性,要保证获取到的bd的属性是正确的

那么问题来了,为什么获取到的bd中属性可能不正确呢?

主要两个原因:

  1. 作为子bd,属性本身就有可能缺失,比如我们在开头介绍的例子,子bd中本身就没有age属性,age属性在父bd
  2. Spring提供了很多扩展点,在启动容器的时候,可能会修改bd中的属性。比如一个正常实现了BeanFactoryPostProcessor就能修改容器中的任意的bd的属性。在后面的容器的扩展点中我再介绍

合并的代码分析:

因为合并的代码其实很简单,所以一并在这里分析了,也可以加深对合并的理解:

  1. protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
  2. // Quick check on the concurrent map first, with minimal locking.
  3. // 从缓存中获取合并后的bd
  4. RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
  5. if (mbd != null) {
  6. return mbd;
  7. }
  8. // 如何获取不到的话,开始真正的合并
  9. return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
  10. }
  11. protected RootBeanDefinition getMergedBeanDefinition(
  12. String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
  13. throws BeanDefinitionStoreException {
  14. synchronized (this.mergedBeanDefinitions) {
  15. RootBeanDefinition mbd = null;
  16. // Check with full lock now in order to enforce the same merged instance.
  17. if (containingBd == null) {
  18. mbd = this.mergedBeanDefinitions.get(beanName);
  19. }
  20. if (mbd == null) {
  21. // 如果没有parentName的话直接使用自身合并
  22. // 就是new了RootBeanDefinition然后再进行属性的拷贝
  23. if (bd.getParentName() == null) {
  24. if (bd instanceof RootBeanDefinition) {
  25. mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
  26. }
  27. else {
  28. mbd = new RootBeanDefinition(bd);
  29. }
  30. }
  31. else {
  32. // 需要进行父子的合并
  33. BeanDefinition pbd;
  34. try {
  35. String parentBeanName = transformedBeanName(bd.getParentName());
  36. if (!beanName.equals(parentBeanName)) {
  37. // 这里是递归,在将父子合并时,需要确保父bd已经合并过了
  38. pbd = getMergedBeanDefinition(parentBeanName);
  39. }
  40. else {
  41. // 一般不会进这个判断
  42. // 到父容器中找对应的bean,然后进行合并,合并也发生在父容器中
  43. BeanFactory parent = getParentBeanFactory();
  44. if (parent instanceof ConfigurableBeanFactory) {
  45. pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
  46. }
  47. // 省略异常信息......
  48. }
  49. }
  50. // 省略异常信息......
  51. //
  52. mbd = new RootBeanDefinition(pbd);
  53. //用子bd中的属性覆盖父bd中的属性
  54. mbd.overrideFrom(bd);
  55. }
  56. // 默认设置为单例
  57. if (!StringUtils.hasLength(mbd.getScope())) {
  58. mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
  59. }
  60. // 如果当前的bd是一个被嵌套bd,并且嵌套的bd不是单例的,但是当前的bd又是单例的
  61. // 那么将当前的bd的scope设置为嵌套bd的类型
  62. if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
  63. mbd.setScope(containingBd.getScope());
  64. }
  65. // 将合并后的bd放入到mergedBeanDefinitions这个map中
  66. // 之后还是可能被清空的,因为bd可能被修改
  67. if (containingBd == null && isCacheBeanMetadata()) {
  68. this.mergedBeanDefinitions.put(beanName, mbd);
  69. }
  70. }
  71. return mbd;
  72. }
  73. }

上面这段代码整体不难理解,可能发生疑惑的主要是两个点:

  1. pbd = getMergedBeanDefinition(parentBeanName);

这里进行的是父bd的合并,是方法的递归调用,这是因为在合并的时候父bd可能也还不是一个合并后的bd

  1. containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()

我查了很久的资料,经过验证后发现,如果进行了形如下面的嵌套配置,那么containingBd会不为null

  1. <bean id="luBanService" class="com.dmz.official.service.LuBanService" scope="prototype">
  2. <property name="lookUpService">
  3. <bean class="com.dmz.official.service.LookUpService" scope="singleton"></bean>
  4. </property>
  5. </bean>

在这个例子中,containingBdLuBanService,此时,LuBanService是一个原型的bd,但lookUpService是一个单例的bd,那么这个时候经过合并,LookUpService也会变成一个原型的bd。大家可以拿我这个例子测试一下。

总结:

这篇文章我觉得最重要的是,我们要明白Spring为什么要进行合并,之所以再每次需要用到BeanDefinition都进行一次合并,是为了每次都拿到最新的,最有效的BeanDefinition,因为利用容器提供了一些扩展点我们可以修改BeanDefinition中的属性。关于容器的扩展点,比如上文提到了BeanFactoryPostProcessor以及BeanDefinitionRegistryPostProcessor,我会在后面的几篇文章中一一介绍。

BeanDefinition的学习就到这里了,这个类很重要,是整个Spring的基石,希望大家可以多花时间多研究研究相关的知识。加油,共勉!在这里插入图片描述

扫描下方二维码,关注我的公众号,更多精彩文章在等您!~~
公众号

发表评论

表情:
评论列表 (有 0 条评论,16人围观)

还没有评论,来说两句吧...

相关阅读