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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索