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

点击上方蓝字程序员

给一个关注吧web


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

上篇文章中,咱们学习了BeanDefinition的一些属性,其中有如下几个属性:缓存

//  是否抽象
boolean isAbstract();
// 获取父BeanDefinition的名称
String getParentName();

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

什么是合并?

咱们来看官网上的一段介绍:大概翻译以下:并发

一个BeanDefinition包含了不少的配置信息,包括构造参数,setter 方法的参数还有容器特定的一些配置信息,好比初始化方法,静态工厂方法等等。一个子的BeanDefinition能够从它的父BeanDefinition继承配置信息,不只如此,还能够覆盖其中的一些值或者添加一些本身须要的属性。使用BeanDefinition的父子定义能够减小不少的重复属性的设置,父BeanDefinition能够做为BeanDefinition定义的模板。app

咱们经过一个例子来观察下合并发生了什么,编写一个 Demo 以下:编辑器

<?xml version="1.0" encoding="UTF-8"?>
<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">

 <bean id="parent" abstract="true"
    class="com.dmz.official.merge.TestBean">

  <property name="name" value="parent"/>
  <property name="age" value="1"/>
 </bean>
 <bean id="child"
    class="com.dmz.official.merge.DerivedTestBean"
    parent="parent" >

  <property name="name" value="override"/>
 </bean>
</beans>
public class DerivedTestBean {
 private String name;

 private int age;

    // 省略getter setter方法
}

public class TestBean {
 private String name;

 private String age;

     // 省略getter setter方法
}

public class Main {
 public static void main(String[] args) {
  ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml");
  DerivedTestBean derivedTestBean = (DerivedTestBean) cc.getBean("child");
  System.out.println("derivedTestBean的name = " + derivedTestBean.getName());
  System.out.println("derivedTestBean的age = " + derivedTestBean.getAge());
 }
}

运行:ide


derivedTestBean 的 name = override derivedTestBean 的 age = 1post


在上面的例子中,咱们将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

一、扫描并获取到bd

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

在这里插入图片描述

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

方法源码以下:

 public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,
                List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
 
{
        // .....
     // 省略部分代码,省略的代码主要时用来执行程序员手动调用API注册的容器的后置处理器
        // .....

  // 发生一次bd的合并
        // 这里只会获取实现了BeanDefinitionRegistryPostProcessor接口的Bean的名字
   String[] postProcessorNames =             beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, truefalse);
   for (String ppName : postProcessorNames) {
                // 筛选实现了PriorityOrdered接口的后置处理器
    if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
     currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
     // 去重
     processedBeans.add(ppName);
    }
   }
   // .....
            // 只存在一个internalConfigurationAnnotationProcessor 处理器,用于扫描
         // 这里只会执行了实现了PriorityOrdered跟BeanDefinitionRegistryPostProcessor的后置处理器
   invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
   // .....
         // 这里又进行了一个bd的合并
   postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, truefalse);
   for (String ppName : postProcessorNames) {
                // 筛选实现了Ordered接口的后置处理器
    if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
     currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
     processedBeans.add(ppName);
    }
   }
   // .....
         // 执行的是实现了BeanDefinitionRegistryPostProcessor接口跟Ordered接口的后置处理器
   invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
   boolean reiterate = true;
   while (reiterate) {
    reiterate = false;
            // 这里再次进行了一次bd的合并
    postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, truefalse);
    for (String ppName : postProcessorNames) {
     if (!processedBeans.contains(ppName)) {
                        // 筛选只实现了BeanDefinitionRegistryPostProcessor的后置处理器
      currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
      processedBeans.add(ppName);
      reiterate = true;
     }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    registryProcessors.addAll(currentRegistryProcessors);
                // 执行的是普通的后置处理器,即没有实现任何排序接口(PriorityOrdered或Ordered)
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    currentRegistryProcessors.clear();
   }
        // .....
        // 省略部分代码,这部分代码跟BeanfactoryPostProcessor接口相关,这节bd的合并没有关,下节容器的扩展点中我会介绍
        // .....

 }

你们能够结合我画的图跟上面的代码过一遍流程,只要弄清楚一点就行,即每次调用beanFactory.getBeanNamesForType都进行了一次bd的合并。getBeanNamesForType这个方法主要目的是为了或者指定类型的bd的名称,以后经过bd的名称去找到指定的bd,而后获取对应的 Bean,好比上面方法三次获取的都是BeanDefinitionRegistryPostProcessor这个类型的bd

咱们能够思考一个问题,为何这一步须要合并呢?你们能够带着这个问题继续往下看,在后文我会解释。

二、实例化

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

第一次:

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

public void preInstantiateSingletons() throws BeansException {
    // .....
 // 省略跟合并没有关的代码
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            // .....

第二次:

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

protected <T> doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly)
 throws BeansException 
{
    // .....
    // 省略跟合并没有关的代码
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    checkMergedBeanDefinition(mbd, beanName, args);

    // Guarantee initialization of beans that the current bean depends on.
    String[] dependsOn = mbd.getDependsOn();
    if (dependsOn != null) {
        // ....
    }
    if (mbd.isSingleton()) {
        // ....
    }
    // ....

咱们能够发现这两次合并有一个共同的特色,就是在合并以后立马利用了合并以后的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的属性。在后面的容器的扩展点中我再介绍

合并的代码分析:

由于合并的代码其实很简单,因此一并在这里分析了,也能够加深对合并的理解:

protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
    // Quick check on the concurrent map first, with minimal locking.
    // 从缓存中获取合并后的bd
    RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
    if (mbd != null) {
        return mbd;
    }
    // 如何获取不到的话,开始真正的合并
    return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
 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) {
    mbd = this.mergedBeanDefinitions.get(beanName);
   }

   if (mbd == null) {
                // 若是没有parentName的话直接使用自身合并
                // 就是new了RootBeanDefinition而后再进行属性的拷贝
    if (bd.getParentName() == null) {
     if (bd instanceof RootBeanDefinition) {
      mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
     }
     else {
      mbd = new RootBeanDefinition(bd);
     }
    }
    else {
     // 须要进行父子的合并
     BeanDefinition pbd;
     try {
      String parentBeanName = transformedBeanName(bd.getParentName());
      if (!beanName.equals(parentBeanName)) {
                            // 这里是递归,在将父子合并时,须要确保父bd已经合并过了
       pbd = getMergedBeanDefinition(parentBeanName);
      }
      else {
                            // 通常不会进这个判断
                            // 到父容器中找对应的bean,而后进行合并,合并也发生在父容器中
       BeanFactory parent = getParentBeanFactory();
       if (parent instanceof ConfigurableBeanFactory) {
        pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
       }
       // 省略异常信息......
      }
     }
     // 省略异常信息......
     //
     mbd = new RootBeanDefinition(pbd);
                    //用子bd中的属性覆盖父bd中的属性
     mbd.overrideFrom(bd);
    }

    // 默认设置为单例
    if (!StringUtils.hasLength(mbd.getScope())) {
     mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
    }
                // 当前bd若是内部嵌套了一个bd,而且嵌套的bd不是单例的,可是当前的bd又是单例的
                // 那么将当前的bd的scope设置为嵌套bd的类型
    if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
     mbd.setScope(containingBd.getScope());
    }
    // 将合并后的bd放入到mergedBeanDefinitions这个map中
                // 以后仍是可能被清空的,由于bd可能被修改
    if (containingBd == null && isCacheBeanMetadata()) {
     this.mergedBeanDefinitions.put(beanName, mbd);
    }
   }

   return mbd;
  }
 }

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

  1. pbd = getMergedBeanDefinition(parentBeanName);

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

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

我查了好久的资料,通过验证后发现,若是进行了形以下面的嵌套配置,那么containingBd会不为 null

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

在这个例子中,containingBdLuBanService,此时,LuBanService是一个原型的bd,但lookUpService是一个单例的bd,那么这个时候通过合并,LookUpService也会变成一个原型的bd。你们能够拿我这个例子测试一下。

总结:

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

BeanDefinition的学习就到这里了,这个类很重要,是整个 Spring 的基石,但愿你们能够多花时间多研究研究相关的知识。加油,共勉!

您点的每一个赞,我都认真当成了喜欢




本文分享自微信公众号 - 程序员DMZ(programerDmz)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索