Spring原始碼之預設標籤解析及BeanDefinition註冊

神祕傑克發表於2022-04-08

開篇

上一篇講解了 Spring 中的標籤包含自定義標籤和預設標籤,這兩種方式存在較大不同,所以本文主要講解預設標籤的解析過程。

預設標籤的解析是在 parseDefaultElement 方法中。

ParseBeanDefinitions方法

該方法分別對不同標籤做不同處理。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   //對import標籤處理
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   //對alias標籤處理
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   //對bean標籤處理
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   //對beans標籤處理
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      doRegisterBeanDefinitions(ele);
   }
}

Bean 標籤的解析及註冊

這四種中,我們主要關注對 bean 標籤的解析。bean 標籤的解析是最複雜且重要的。我們進入 processBeanDefinition 方法。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
}

該段程式碼我們還是先看時序圖。

Bean標籤的解析及註冊時序圖

該方法 processBeanDefinition 大致邏輯如下:

  1. 首先呼叫了delegate.parseBeanDefinitionElement(ele)方法進行元素解析。並返回 BeanDefinitionHolder 型別的 bdHolder,經過這個方法後,bdHolder 例項中已經包含了配置檔案中的各種屬性,比如 class,name,id,alias 等
  2. 當返回的 bdHolder 不為空的情況下,若存在預設標籤的子節點下還有自定義屬性,還要對自定義標籤進行解析。
  3. 解析完成後,需要對解析後的 bdHolder 進行註冊,註冊操作委託給了BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 方法
  4. 最後發出響應事件,通知相關監聽器,該 bean 已經載入完成

解析 BeanDefinition

接下來我們一點點分析,首先我們分析該方法delegate.parseBeanDefinitionElement(ele)

該方法在BeanDefinitionParserDelegate類中。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
   return parseBeanDefinitionElement(ele, null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
   //解析id屬性
   String id = ele.getAttribute(ID_ATTRIBUTE);
   //解析name屬性
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   //分割name屬性
   List<String> aliases = new ArrayList<>();
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
   }

   String beanName = id;
   if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
      if (logger.isTraceEnabled()) {
         logger.trace("No XML 'id' specified - using '" + beanName +
               "' as bean name and " + aliases + " as aliases");
      }
   }

   if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
   }
   // 程式碼(1)
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   if (beanDefinition != null) {
      if (!StringUtils.hasText(beanName)) {
         try {
            //如果不存在beanName那麼根據Spring中提供的命名規則為當前bean生成對應的beanName
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            }
            else {
               beanName = this.readerContext.generateBeanName(beanDefinition);
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
            if (logger.isTraceEnabled()) {
               logger.trace("Neither XML 'id' nor 'name' specified - " +
                     "using generated bean name [" + beanName + "]");
            }
         }
         catch (Exception ex) {
            error(ex.getMessage(), ele);
            return null;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }

   return null;
}

該方法就是對預設標籤解析的全過程,我們現在可以看到對屬性 id、name 的解析。

在當前方法主要完成的內容如下:

  1. 提取元素 id、name 屬性
  2. 解析其他屬性並封裝到 GenericBeanDefinition 型別例項中
  3. 如果檢測到 bean 沒有指定 beanName,則使用預設規則生成一個 beanName
  4. 將獲取到的資訊封裝到 BeanDefinitionHolder 例項中

我們看一下程式碼中標註的程式碼(1)呼叫的parseBeanDefinitionElement方法是如何對其他標籤進行解析的。

public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, @Nullable BeanDefinition containingBean) {

   this.parseState.push(new BeanEntry(beanName));
   String className = null;
   //解析class屬性
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   String parent = null;
   //解析parent屬性
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }

   try {
      //程式碼(1)建立用於承載屬性的AbstractBeanDefinition型別的GenericBeanDefinition
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            //程式碼(2)解析預設bean的各種屬性
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      //提取 description
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      //程式碼(3)解析後設資料
      parseMetaElements(ele, bd);
      //解析lookup-medthod屬性 (用的很少,這裡就不深入介紹)
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      //解析replace-medthod屬性(用的很少,這裡就不深入介紹)
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      //程式碼(4)解析建構函式引數
      parseConstructorArgElements(ele, bd);
      //程式碼(5)解析property子元素
      parsePropertyElements(ele, bd);
      //解析qualifier子元素
      parseQualifierElements(ele, bd);

      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));

      return bd;
   }
   catch (ClassNotFoundException ex) {
      error("Bean class [" + className + "] not found", ele, ex);
   }
   catch (NoClassDefFoundError err) {
      error("Class that bean class [" + className + "] depends on not found", ele, err);
   }
   catch (Throwable ex) {
      error("Unexpected failure during bean definition parsing", ele, ex);
   }
   finally {
      this.parseState.pop();
   }

   return null;
}

建立用於屬性承載的 BeanDefinition

我們先看一下程式碼(1)呼叫的方法之前,我們先再瞭解一下 BeanDefinition。

BeanDefinition 是一個介面,在 Spring 中存在三種實現:RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition。它們均繼承自AbstractBeanDefinition,其中 BeanDefinition 是配置檔案<bean>元素在容器內部的表現形式。該標籤擁有 class、scope、lazy-init 等配置屬性,BeanDefinition 也提供了對應的屬性:beanClass、scope、lazyInit。

其中 RootBeanDefinition 是最常用的實現類,一般對應<bean>元素標籤,而 GenericBeanDefinition 是 2.5 版本後加入的 bean 檔案配置屬性定義類,提供一站式服務類。

在配置檔案中我們可以父<bean>和子<bean>,父就用 RootBeanDefinition 表示,而子就使用 ChildBeanDefinition 表示。普通的<bean>就使用 RootBeanDefinition 來表示,AbstractBeanDefinition 則對兩者共同類的資訊進行抽象。

Spring 通過 BeanDefinition 將配置檔案的<bean>轉換為容器內部表示,並且將這些 BeanDefinition 註冊到 BeanDefinitionRegistry 中。

Spring 容器的 BeanDefinitionRegistry 主要以 map 形式儲存,後續操作可以直接從該類中獲取配置資訊。

但首先,我們解析屬性之前就需要建立用於承載屬性的例項,也就是建立了我們之前說的 GenericBeanDefinition 型別的例項。也就是程式碼(1)呼叫的createBeanDefinition(className, parent)方法。

protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
      throws ClassNotFoundException {

   return BeanDefinitionReaderUtils.createBeanDefinition(
         parentName, className, this.readerContext.getBeanClassLoader());
}
public static AbstractBeanDefinition createBeanDefinition(
      @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

   GenericBeanDefinition bd = new GenericBeanDefinition();
   //如果沒有父類,parentName則為空
   bd.setParentName(parentName);
   if (className != null) {
      //如果classLoader不為空則使用傳入的classLoader進行載入類物件,否則只是記錄className
      if (classLoader != null) {
         bd.setBeanClass(ClassUtils.forName(className, classLoader));
      }else {
         bd.setBeanClassName(className);
      }
   }
   return bd;
}

至此,我們就建立好了 GenericBeanDefinition 例項。

解析各種屬性

當建立完用來承載 Bean 資訊的 GenericBeanDefinition 例項後,就可以對 bean 資訊的各種屬性進行解析了。

首先我們進入程式碼(2)parseBeanDefinitionAttributes方法,該方法對 element 所有元素屬性進行解析。

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
      @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

    // 解析singleton屬性
   if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
      // singleton屬性已經不被支援,使用scope代替
      error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
   }
   //解析scope屬性
   else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
      bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
   }
   else if (containingBean != null) {
      //在嵌入BeanDefinition情況下,並且沒有單獨指定scope屬性,則使用父類預設的屬性
      bd.setScope(containingBean.getScope());
   }
   //解析abstract屬性
   if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
      bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
   }
   //解析lazy-init屬性
   String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
   if (isDefaultValue(lazyInit)) {
      lazyInit = this.defaults.getLazyInit();
   }
   bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
   //解析autowire屬性
   String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
   bd.setAutowireMode(getAutowireMode(autowire));
    // 解析depends-on屬性
   if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
      String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
      bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
   }
    // 解析autowire-candidate屬性
   String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
   if (isDefaultValue(autowireCandidate)) {
      String candidatePattern = this.defaults.getAutowireCandidates();
      if (candidatePattern != null) {
         String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
         bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
      }
   }
   else {
      bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
   }

    // 解析primary屬性
   if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
      bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
   }

   //解析init-method屬性
   if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
      String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
      bd.setInitMethodName(initMethodName);
   }
   else if (this.defaults.getInitMethod() != null) {
      bd.setInitMethodName(this.defaults.getInitMethod());
      bd.setEnforceInitMethod(false);
   }
   // 解析destroy-method屬性
   if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
      String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
      bd.setDestroyMethodName(destroyMethodName);
   }
   else if (this.defaults.getDestroyMethod() != null) {
      bd.setDestroyMethodName(this.defaults.getDestroyMethod());
      bd.setEnforceDestroyMethod(false);
   }
   // 解析factory-method屬性
   if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
      bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
   }
   // 解析factory-bean屬性
   if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
      bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
   }

   return bd;
}

該方法主要做的就是拿到各種屬性對應的屬性值放入 AbstractBeanDefinition 對應屬性中。

解析子元素 meta

首先我們回顧一下如何使用 meta 屬性。

<bean id="myTestBean" class="cn.jack.MyTestBean">
        <meta key="jack" value="HelloWorld"/>
</bean>
public class MyTestBean {

    private String testStr = "testStr";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }
}

這段程式碼並沒有體現在 MyTestBean 中,只是一個宣告,在使用的時候可以使用BeanDefinition類的getAttribute(key)方法獲取。

接下來我們看一下是如何解析的,進入程式碼(3)。

public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
   //獲取當前節點所有元素
   NodeList nl = ele.getChildNodes();
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      //判斷節點是否為meta
      if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
         Element metaElement = (Element) node;
         String key = metaElement.getAttribute(KEY_ATTRIBUTE);
         String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
         //構造BeanMetadataAttribute例項
         BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
         attribute.setSource(extractSource(metaElement));
         //記錄資訊
         attributeAccessor.addMetadataAttribute(attribute);
      }
   }
}

解析子元素 constructor-arg

對建構函式的解析還是非常常見的,同時也是很複雜,舉個例子:

<bean id="myTestBean" class="cn.jack.MyTestBean">
        <constructor-arg index="0">
            <value>Jack</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>hello</value>
        </constructor-arg>
</bean>

該程式碼就是 Spring 中最基礎的配置,自動尋找對應的構造器並在初始化的時候將設定的引數傳入進去,接下來看一下如何解析。

public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
   //拿到bean所有子節點
   NodeList nl = beanEle.getChildNodes();
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
         //解析constructor-arg
         parseConstructorArgElement((Element) node, bd);
      }
   }
}

進入parseConstructorArgElement方法。

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
        //提取index屬性
        String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
        //提取type屬性
        String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
        //提取name屬性
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
        if (StringUtils.hasLength(indexAttr)) {
            try {
                int index = Integer.parseInt(indexAttr);
                if (index < 0) {
                    error("'index' cannot be lower than 0", ele);
                }
                else {
                    try {
                        this.parseState.push(new ConstructorArgumentEntry(index));
                        //程式碼(1)解析ele對應的屬性元素
                        Object value = parsePropertyValue(ele, bd, null);
                        //使用ConstructorArgumentValues.ValueHolder型別封裝解析出來的元素
                        ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
                        //將name屬性和type都封裝到valueHolder中
                        if (StringUtils.hasLength(typeAttr)) {
                            valueHolder.setType(typeAttr);
                        }
                        if (StringUtils.hasLength(nameAttr)) {
                            valueHolder.setName(nameAttr);
                        }
                        valueHolder.setSource(extractSource(ele));
                        //不允許重複指定相同引數
                        if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                            error("Ambiguous constructor-arg entries for index " + index, ele);
                        }
                        else {
                            //新增到BeanDefinition的ConstructorArgumentValues中,存入結構為Map
                            bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
                        }
                    }
                    finally {
                        this.parseState.pop();
                    }
                }
            }
            catch (NumberFormatException ex) {
                error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
            }
        }
        else {
            //index為空的處理
            try {
                this.parseState.push(new ConstructorArgumentEntry());
                //解析ele節點對應的屬性值
                Object value = parsePropertyValue(ele, bd, null);
                //使用ConstructorArgumentValues.ValueHolder型別封裝解析出來的元素
                ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
                //將name屬性和type都封裝到valueHolder中
                if (StringUtils.hasLength(typeAttr)) {
                    valueHolder.setType(typeAttr);
                }
                if (StringUtils.hasLength(nameAttr)) {
                    valueHolder.setName(nameAttr);
                }
                valueHolder.setSource(extractSource(ele));
                //新增到BeanDefinition的ConstructorArgumentValues中,因為沒有index則存入結構為List
                bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
            }
            finally {
                this.parseState.pop();
            }
        }
}

該方法並不是特別複雜,首先提取 constructor-arg 上必要的屬性(index、type、name)。

配置中指定了 index 的話操作步驟如下:

  1. 解析 constructor-arg 的子元素
  2. 使用 ConstructorArgumentValues.ValueHolder 型別封裝解析後的元素
  3. 最後將 index、name、type 封裝到 ValueHolder 型別中,並新增到 BeanDefinition 的 constructorArgumentValues 的 indexedArgumentValues 屬性中。

配置中沒有指定 index 的話操作步驟如下:

  1. 解析 constructor-arg 的子元素
  2. 使用 ConstructorArgumentValues.ValueHolder 型別封裝解析後的元素
  3. 最後將 index、name、type 封裝到 ValueHolder 型別中,並新增到 BeanDefinition 的 constructorArgumentValues 的 genericArgumentValues 屬性中。

瞭解完流程之後,我們看一下具體是如何進行解析的,進入程式碼(1)parsePropertyValue的方法中。

public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
   String elementName = (propertyName != null ?
         "<property> element for property '" + propertyName + "'" :
         "<constructor-arg> element");

   // 獲取ele節點的子節點,一個屬性只能對應一種型別:ref/value/list等
   NodeList nl = ele.getChildNodes();
   Element subElement = null;
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      //跳過meta節點或description節點
      if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
            !nodeNameEquals(node, META_ELEMENT)) {
         //只能有一個子節點,否則異常
         if (subElement != null) {
            error(elementName + " must not contain more than one sub-element", ele);
         }
         else {
            //把子節點賦值給subElement
            subElement = (Element) node;
         }
      }
   }
       //解析constructor-arg的ref屬性
   boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
   //解析constructor-arg的value屬性
   boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
   //在constructor-arg中不存在: 1.既有ref又有value屬性 2.存在ref或者value屬性並且有子元素
   if ((hasRefAttribute && hasValueAttribute) ||
         ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
      error(elementName +
            " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
   }

   if (hasRefAttribute) {
      // ref屬性處理,使用RuntimeBeanReference封裝對應的ref名稱
      String refName = ele.getAttribute(REF_ATTRIBUTE);
      if (!StringUtils.hasText(refName)) {
         error(elementName + " contains empty 'ref' attribute", ele);
      }
      RuntimeBeanReference ref = new RuntimeBeanReference(refName);
      ref.setSource(extractSource(ele));
      return ref;
   }
   else if (hasValueAttribute) {
      //value屬性的處理,使用TypedStringValue封裝
      TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
      valueHolder.setSource(extractSource(ele));
      return valueHolder;
   }
   else if (subElement != null) {
      //解析子元素
      return parsePropertySubElement(subElement, bd);
   }
   else {
      //如果沒有ref和value,也沒有子元素則丟擲異常
      // Neither child element nor "ref" or "value" attribute found.
      error(elementName + " must specify a ref or value", ele);
      return null;
   }
}

該方法對建構函式中屬性的元素解析,經過以下過程:

  1. 跳過 description 或者 meta
  2. 提取 constructor-arg 上的 ref 和 value 屬性,隨後進行校驗
  3. ref 屬性處理,使用 RuntimeBeanReference 封裝對應的 ref 名稱,比如:

    <constructor-arg ref="a"></constructor-arg>
  4. value 屬性的處理,使用 TypedStringValue 封裝,比如:

    <constructor-arg value="a"></constructor-arg>
  5. 子元素處理,比如:

    <bean id="myTestBean" class="cn.jack.MyTestBean">
            <constructor-arg>
                <map>
                    <entry key="jack" value="nihao"></entry>
                </map>
            </constructor-arg>
    </bean>

對於子元素的處理,比如這裡提到的加入了 map 元素,是如何處理的?具體在parsePropertySubElement中實現了各種子元素的處理。

public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
   return parsePropertySubElement(ele, bd, null);
}
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
        //判斷是否為預設名稱空間,如果不是就進行解析自定義節點
        if (!isDefaultNamespace(ele)) {
            return parseNestedCustomElement(ele, bd);
        }
        //解析是否為bean節點
        else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
            BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
            if (nestedBd != null) {
                nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
            }
            return nestedBd;
        }
        //解析ref標籤
        else if (nodeNameEquals(ele, REF_ELEMENT)) {
            // A generic reference to any name of any bean.
            String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
            boolean toParent = false;
            if (!StringUtils.hasLength(refName)) {
                // A reference to the id of another bean in a parent context.
                //解析parent
                refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
                toParent = true;
                if (!StringUtils.hasLength(refName)) {
                    error("'bean' or 'parent' is required for <ref> element", ele);
                    return null;
                }
            }
            if (!StringUtils.hasText(refName)) {
                error("<ref> element contains empty target attribute", ele);
                return null;
            }
            RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
            ref.setSource(extractSource(ele));
            return ref;
        }
        //解析idref元素
        else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
            return parseIdRefElement(ele);
        }
        //解析value元素
        else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
            return parseValueElement(ele, defaultValueType);
        }
        //解析null元素
        else if (nodeNameEquals(ele, NULL_ELEMENT)) {
            // It's a distinguished null value. Let's wrap it in a TypedStringValue
            // object in order to preserve the source location.
            TypedStringValue nullHolder = new TypedStringValue(null);
            nullHolder.setSource(extractSource(ele));
            return nullHolder;
        }
        //解析array元素
        else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
            return parseArrayElement(ele, bd);
        }
        //解析list元素
        else if (nodeNameEquals(ele, LIST_ELEMENT)) {
            return parseListElement(ele, bd);
        }
        //解析set元素
        else if (nodeNameEquals(ele, SET_ELEMENT)) {
            return parseSetElement(ele, bd);
        }
        //解析map元素
        else if (nodeNameEquals(ele, MAP_ELEMENT)) {
            return parseMapElement(ele, bd);
        }
        //解析props元素
        else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
            return parsePropsElement(ele);
        }
        else {
            error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
            return null;
        }
}

在該方法中實現了所有支援的型別的分類處理,到此就已經理清楚建構函式是如何解析了,這裡就不深入研究如何解析 list、map 等元素了。

解析子元素 property

在分析完建構函式後,我們可以接著往下看,這裡避免忘記我們再看一下目前到哪裡了。

parseBeanDefinitionElement

到這裡我們先回顧一下如何使用 property 屬性。當然,property 屬性裡也可以使用 list 等型別的元素。

<bean id="myTestBean" class="cn.jack.MyTestBean">
   <property name="testStr" value="jack"/>
</bean>
public class MyTestBean {

   private String testStr = "testStr";

   public String getTestStr() {
      return testStr;
   }

   public void setTestStr(String testStr) {
      this.testStr = testStr;
   }
}

接下來我們看一下是如何解析的。

public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
   //獲取到beanElement的所有子節點
   NodeList nl = beanEle.getChildNodes();
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
         //解析property節點
         parsePropertyElement((Element) node, bd);
      }
   }
}
public void parsePropertyElement(Element ele, BeanDefinition bd) {
   //獲取配置元素的name值
   String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
   if (!StringUtils.hasLength(propertyName)) {
      //name為空則丟擲異常
      error("Tag 'property' must have a 'name' attribute", ele);
      return;
   }
   this.parseState.push(new PropertyEntry(propertyName));
   try {
      //校驗在相同bean節點下,是否存在同樣的name屬性,如果存在則丟擲異常
      if (bd.getPropertyValues().contains(propertyName)) {
         error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
         return;
      }
      //解析屬性值
      Object val = parsePropertyValue(ele, bd, propertyName);
      //解析後的值和name屬性封裝為PropertyValue
      PropertyValue pv = new PropertyValue(propertyName, val);
      //解析meta節點
      parseMetaElements(ele, pv);
      pv.setSource(extractSource(ele));
      //解析完成後新增到BeanDefinition的propertyValues屬性中
      bd.getPropertyValues().addPropertyValue(pv);
   }
   finally {
      this.parseState.pop();
   }
}

和之前講解的過程都差不多,都是先獲取所有子標籤然後進行遍歷進行解析,獲取對應的 name、value 值進行封裝。

解析子元素 qualifier

該元素我們一般使用註解偏多,主要就是當介面存在多個實現類時候,在我們注入時指定某一個實現類,這樣 Spring 容器就可以找到對應的 bean。因為在 Spring 中候選的 Bean 數目必須有且僅有一個。解析過程和之前都差不多,這裡就不再贅述。

<bean id="myTestBean" class="cn.jack.MyTestBean">
   <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="Bean的名稱"/>
</bean>

AbstractBeanDefinition 屬性

AbstractBeanDefinition bd = createBeanDefinition(className, parent);

至此,我們就完成了對 XML 文件到 GenericBeanDefinition 的轉換,XML 中的配置都可以在 GenericBeanDefinition 中看到,但 GenericBeanDefinition 只是子類,大部分屬性都在 AbstractBeanDefinition 中。我們回顧一下都有哪些配置。

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
        implements BeanDefinition, Cloneable {

    // 此處省略靜態變數以及final變數

    @Nullable
    private volatile Object beanClass;
    /**
     * bean的作用範圍,對應bean屬性scope
     */
    @Nullable
    private String scope = SCOPE_DEFAULT;
    /**
     * 是否是抽象,對應bean屬性abstract
     */
    private boolean abstractFlag = false;
    /**
     * 是否延遲載入,對應bean屬性lazy-init
     */
    private boolean lazyInit = false;
    /**
     * 自動注入模式,對應bean屬性autowire
     */
    private int autowireMode = AUTOWIRE_NO;
    /**
     * 依賴檢查,Spring 3.0後棄用這個屬性
     */
    private int dependencyCheck = DEPENDENCY_CHECK_NONE;
    /**
     * 用來表示一個bean的例項化依靠另一個bean先例項化,對應bean屬性depend-on
     */
    @Nullable
    private String[] dependsOn;
    /**
     * autowire-candidate屬性設定為false,這樣容器在查詢自動裝配物件時,
     * 將不考慮該bean,即它不會被考慮作為其他bean自動裝配的候選者,
     * 但是該bean本身還是可以使用自動裝配來注入其他bean的
     */
    private boolean autowireCandidate = true;
    /**
     * 自動裝配時出現多個bean候選者時,將作為首選者,對應bean屬性primary
     */
    private boolean primary = false;
    /**
     * 用於記錄Qualifier,對應子元素qualifier
     */
    private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>(0);

    @Nullable
    private Supplier<?> instanceSupplier;
    /**
     * 允許訪問非公開的構造器和方法,程式設定
     */
    private boolean nonPublicAccessAllowed = true;
    /**
     * 是否以一種寬鬆的模式解析建構函式,預設為true,
     * 如果為false,則在以下情況
     * interface ITest{}
     * class ITestImpl implements ITest{};
     * class Main {
     *     Main(ITest i) {}
     *     Main(ITestImpl i) {}
     * }
     * 丟擲異常,因為Spring無法準確定位哪個建構函式程式設定
     */
    private boolean lenientConstructorResolution = true;
    /**
     * 對應bean屬性factory-bean,用法:
     * <bean id = "instanceFactoryBean" class = "example.chapter3.InstanceFactoryBean" />
     * <bean id = "currentTime" factory-bean = "instanceFactoryBean" factory-method = "createTime" />
     */
    @Nullable
    private String factoryBeanName;
    /**
     * 對應bean屬性factory-method
     */
    @Nullable
    private String factoryMethodName;
    /**
     * 記錄建構函式注入屬性,對應bean屬性constructor-arg
     */
    @Nullable
    private ConstructorArgumentValues constructorArgumentValues;
    /**
     * 普通屬性集合
     */
    @Nullable
    private MutablePropertyValues propertyValues;
    /**
     * 方法重寫的持有者,記錄lookup-method、replaced-method元素
     */
    @Nullable
    private MethodOverrides methodOverrides;
    /**
     * 初始化方法,對應bean屬性init-method
     */
    @Nullable
    private String initMethodName;
    /**
     * 銷燬方法,對應bean屬性destroy-method
     */
    @Nullable
    private String destroyMethodName;
    /**
     * 是否執行init-method,程式設定
     */
    private boolean enforceInitMethod = true;
    /**
     * 是否執行destroy-method,程式設定
     */
    private boolean enforceDestroyMethod = true;
    /**
     * 是否是使用者定義的而不是應用程式本身定義的,建立AOP時候為true,程式設定
     */
    private boolean synthetic = false;
    /**
     * 定義這個bean的應用,APPLICATION:使用者,INFRASTRUCTURE:完全內部使用,與使用者無關,
     * SUPPORT:某些複雜配置的一部分
     * 程式設定
     */
    private int role = BeanDefinition.ROLE_APPLICATION;
    /**
     * bean的描述資訊
     */
    @Nullable
    private String description;
    /**
     * 這個bean定義的資源
     */
    @Nullable
    private Resource resource;
}

解析預設標籤中的自定義標籤元素

processBeanDefinition

到目前為止,我們已經分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);這行程式碼,接下來我們繼續分析bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);這行程式碼。

我們先了解一下這行程式碼的大概作用,從語義上來分析:如果需要的話就對 BeanDefinition 進行裝飾,類似於如下場景:

<bean id="myTestBean" class="cn.jack.MyTestBean">
        <mybean:user username="jack"/>
</bean>

當 Spring 中的 bean 使用的是預設標籤配置,但是子元素卻使用自定義配置的時候,這行程式碼就會執行。

但是為什麼會在預設型別解析中單獨新增一個自定義型別呢?首先這個自定義型別並不是以 bean 的形式出現的,在這裡的自定義型別其實相當於是屬性。

我們繼續分析該方法程式碼。

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
   return decorateBeanDefinitionIfRequired(ele, originalDef, null);
}

在呼叫decorateBeanDefinitionIfRequired方法時,第三個引數傳入為 null,該引數是父類 bean,當對某個巢狀配置分析時需要傳遞父類的 BeanDefinition,其實就是為了使用父類的 scope 屬性,如果子類沒有設定 scope 屬性則使用父類的 scope 屬性。這裡是頂層配置,所以傳遞為 null。

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

        BeanDefinitionHolder finalDefinition = originalDef;

        // 遍歷節點,檢視是否存在適用於裝飾的屬性
        // Decorate based on custom attributes first.
        NamedNodeMap attributes = ele.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
        //遍歷子節點,檢視是否存在適用於裝飾的屬性
        // Decorate based on custom nested elements.
        NodeList children = ele.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
            }
        }
        return finalDefinition;
}

最終都呼叫到了decorateIfRequired方法,我們進入此方法檢視。

public BeanDefinitionHolder decorateIfRequired(
      Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
       //獲取自定義名稱空間
   String namespaceUri = getNamespaceURI(node);
   // 過濾預設名稱空間
   if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
      //根據名稱空間找到相應的處理器
      NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
      if (handler != null) {
         //進行裝飾處理
         BeanDefinitionHolder decorated =
               handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
         if (decorated != null) {
            return decorated;
         }
      }
      else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
         error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
      }
      else {
         // A custom namespace, not to be handled by Spring - maybe "xml:...".
         if (logger.isDebugEnabled()) {
            logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
         }
      }
   }
   return originalDef;
}

到這裡已經很明確了,首先獲取元素或者屬性的名稱空間,然後判斷是否適用於自定義標籤的解析條件,隨後找到對應的 NamespaceHandler 進行下一步解析,該部分會在自定義標籤解析中講解。

總結:該方法的作用就是對自定義標籤或者自定義屬性進行處理,然後找到對應的名稱空間處理器進行進一步的解析。

註冊解析的 BeanDefinition

到這裡,我們對配置檔案的解析、裝飾都已經完成,現在的 BeanDefinition 已經滿足使用要求了,後續就剩下了註冊工作。

也就是 processBeanDefinition 方法中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());這行程式碼。

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {

   //獲取beanName做唯一標識註冊
   String beanName = definitionHolder.getBeanName();
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

   //如果有別名的話,註冊所有別名
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}

該方法獲取到 beanName 後,最終 BeanDefinition 都會註冊到BeanDefinitionRegistry中,該方法分為兩部分,一種為 beanName 註冊方式和別名註冊方式。

對於 BeanDefinition 的註冊,不僅僅是將 BeanDefinition 放入 map 中,然後 beanName 作為 key。除此之外還做了別的事情。

進入 DefaultListableBeanFactory 類實現中。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                //註冊前最後一次校驗,針對AbstractBeanDefinition中的methodOverrides校驗
                //校驗methodOverrides是否與工廠方法並存,或者methodOverrides對應的方法壓根不存在
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }
        //判斷是否已經存在bean
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            //如果對應的beanName已經註冊過並且不允許覆蓋,則丟擲異常
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            //存入BeanDefinition
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            //是否已經開始建立bean
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                // 因為beanDefinitionMap是全域性變數,這裡會存在併發訪問的情況
                synchronized (this.beanDefinitionMap) {
                    //存入BeanDefinition
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    removeManualSingletonName(beanName);
                }
            }
            else {
                // Still in startup registration phase
                //存入BeanDefinition
                this.beanDefinitionMap.put(beanName, beanDefinition);
                //記錄beanName
                this.beanDefinitionNames.add(beanName);
                //從factoryBeanCreatedCache中移除掉這個beanName
                removeManualSingletonName(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            // 重置所有beanName對應的快取
            resetBeanDefinition(beanName);
        }
}

註冊 bean 分為以下四步:

  1. 對 AbstractBeanDefinition 的 methodOverrides 屬性校驗
  2. 如果已經註冊過並且不允許覆蓋則丟擲異常,否則直接覆蓋
  3. 加入 map 快取
  4. 清除解析前的 beanName 快取

之後我們再看通過別名註冊就簡單多了。

public void registerAlias(String name, String alias) {
   Assert.hasText(name, "'name' must not be empty");
   Assert.hasText(alias, "'alias' must not be empty");
   synchronized (this.aliasMap) {
      //如果beanName與alias相同則不記錄alias,並刪除對應的alias
      if (alias.equals(name)) {
         this.aliasMap.remove(alias);
         if (logger.isDebugEnabled()) {
            logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
         }
      }
      else {
         String registeredName = this.aliasMap.get(alias);
         if (registeredName != null) {
            if (registeredName.equals(name)) {
               // An existing alias - no need to re-register
               return;
            }
            //不允許覆蓋則丟擲異常
            if (!allowAliasOverriding()) {
               throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                     name + "': It is already registered for name '" + registeredName + "'.");
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                     registeredName + "' with new target name '" + name + "'");
            }
         }
         //確保新增的沒有name和alias值相反的資料且alias和name不相等
         checkForAliasCircle(name, alias);
         //存入map中
         this.aliasMap.put(alias, name);
         if (logger.isTraceEnabled()) {
            logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
         }
      }
   }
}

從該方法可知,註冊 alias 步驟如下:

  1. alias 和 beanName 相同情況處理,如果相同則不需要處理並刪除原有 alias
  2. 覆蓋校驗處理
  3. alias 迴圈檢查
  4. 註冊 alias

通知監聽器解析及註冊完成

通知監聽器解析及註冊完成

在註冊完成後,當開發人員需要對註冊 BeanDefinition 事件進行監聽時可以通過註冊監聽器方式將處理邏輯寫入監聽器中,在 Spring 中並沒有對此事件做任何邏輯處理。

總結

到這裡,Bean 的解析和註冊過程已經全部 OK 了。

回顧一下,解析 BeanDefinition 的入口在 DefaultBeanDefinitionDocumentReader.parseBeanDefinitions() 。該方法會根據命令空間來判斷標籤是預設標籤還是自定義標籤,其中預設標籤由 parseDefaultElement() 實現,自定義標籤由 parseCustomElement() 實現。在預設標籤解析中,會根據標籤名稱的不同進行 import 、alias 、bean 、beans 四大標籤進行處理,其中 bean 標籤的解析為核心,它由 processBeanDefinition() 方法實現。processBeanDefinition() 開始進入解析核心工作,分為三步:

  1. 解析預設標籤:BeanDefinitionParserDelegate.parseBeanDefinitionElement()
  2. 解析預設標籤下的自定義標籤:BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired()
  3. 註冊解析的 BeanDefinition:BeanDefinitionReaderUtils.registerBeanDefinition()

在預設標籤解析過程中,核心工作由 parseBeanDefinitionElement() 方法實現,該方法會依次解析 Bean 標籤的屬性、各個子元素,解析完成後返回一個 GenericBeanDefinition 例項物件。

最後通過registerBeanDefinition方法進行對BeanDefinition進行註冊後就大功告成了。

相關文章