1. 程式人生 > >Spring原始碼——預設標籤解析

Spring原始碼——預設標籤解析

前言

內容主要參考自《Spring原始碼深度解析》一書,算是讀書筆記或是原書的補充。進入正文後可能會引來各種不適,畢竟閱讀原始碼是件極其痛苦的事情。

上一篇文章中,我們已經對Spring讀取配置檔案註冊Bean的流程大致瞭解了,著重跟隨程式碼的流程一步步檢視Spring是如何為DI進行準備的。當然我們最終沒有看到它是如何一步步解析XML中的標籤並生成我們的依賴物件並進行注入的,那麼本文接著來繼續學習Spring是如何解析內建標籤的。

I. bean標籤的解析及註冊

預設標籤解析是在 parseDefaultElement 中實現的,函式的功能主要是對四種標籤(import,alias,bean 和 beans)進行不同的解析。

/**
 * 解析預設標籤<import> <alias> <bean> 巢狀的<beans>
 * @param ele 每一個標籤
 * @param delegate 翻譯為:bean定義解析的代表
 */
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)) { // recurse
doRegisterBeanDefinitions(ele); } }

IDEA編寫提示

四種標籤中,其中 bean 標籤最為複雜常見,下面先介紹 bean 標籤的解析過程。點進 processBeanDefinition 函式:

/**
 * 解析<bean>標籤
 * Process the given bean element, parsing the bean definition
 * and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 委託BeanDefinitionParserDelegate對ele進行解析,bdHolder已經包含配置檔案中配置的各種屬性,例如class,name,id,alias
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 預設標籤下若存在自定義屬性,還需要再次對自定義標籤進行解析
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance. 解析完成,需要對解析後的btHolder進行註冊
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                                     bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event. 最後發出註冊響應事件,通知相關的監聽器,這個bean已經載入完成
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

傳入的 Element ele 相當於一個 <bean>···</bean> 包含的內容。該函式的時序邏輯如下圖所示:

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

該函式主要完成四件事:

  • 委託 BeanDefinitionParserDelegateele 進行解析,返回的 BeanDefinitionHolder 型別的 bdHolder 已經包含配置檔案中對該 bean 配置的各種屬性,例如 class, name, id, alias
  • 如果返回的 bdHolder 不為空,預設 bean 標籤下若存在自定義屬性,還需要再次對自定義標籤進行解析;
  • 解析完成,需要對解析後的btHolder進行註冊。同樣,註冊操作交給了 BeanDefinitionReaderUtilsregisterBeanDefinition 方法去完成;
  • 最後發出註冊響應事件,通知相關的監聽器,這個bean已經載入完成。

解析BeanDefinition

下面就針對上面的四步驟進行一一詳細跟蹤。從元素解析及資訊提取開始,我們點進 BeanDefinitionParserDelegateparseBeanDefinitionElement 方法:

/**
 * bean標籤資訊提取
 * Parses the supplied {@code <bean>} element. May return {@code null}
 * if there were errors during parse. Errors are reported to the
 * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
 */
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

/**
 * bean標籤資訊提取
 * Parses the supplied {@code <bean>} element. May return {@code null}
 * if there were errors during parse. Errors are reported to the
 * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
 */
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 1. 獲取id和name屬性值
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // name可能設定了多個,要解析成陣列新增至aliases
    List<String> aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {
        // 以','、';'或' '分割
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }

    // 預設設定了id屬性,則bean的名稱就為id
    // 如果id不存在name屬性存在,則bean的名稱設定為alias的第一個元素,剩下的作為別名
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
        if (logger.isDebugEnabled()) {
            logger.debug("No XML 'id' specified - using '" + beanName +
                         "' as bean name and " + aliases + " as aliases");
        }
    }

    if (containingBean == null) {
        // 檢驗bean是否已經被用,沒有重複則儲存bean的名稱與別名
        checkNameUniqueness(beanName, aliases, ele);
    }

    // 2. 進一步解析其他所有屬性
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        // 3. 如果id和name屬性都沒有指定,則Spring會自行建立beanName以及指定別名
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                        beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // Register an alias for the plain bean class name, if still possible,
                    // if the generator returned the class name plus a suffix.
                    // This is expected for Spring 1.2/2.0 backwards compatibility.
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null &&
                        beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                        !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("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);
        // 4. 將解析完成的資訊封裝至BeanDefinitionHolder例項中
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
    return null;
}

通過閱讀原始碼以及註釋,可以看到,該函式也主要分成四個步驟:

  1. 在全面解析所有屬性前,先提取元素中的 idname 屬性;
  2. 進一步解析其他所有屬性,並封裝資訊至 GenericBeanDefinition 型別的例項物件 beanDefinition 中;
  3. 如果檢測到 bean 沒有指定的 beanName,那麼Spring使用預設規則給該 bean 生成 beanName;
  4. 將解析出的所有資訊封裝到 BeanDefinitionHolder 例項物件中。

第 1 步邏輯簡單,而關於 Spring 對 bean 的 name設定和我們平時熟知的規則是一致的。我們進一步檢視步驟 2 解析其他屬性的函式 parseBeanDefinitionElement(ele, beanName, containingBean)

/**
 * bean標籤除了id和name其他屬性資訊的解析
 * Parse the bean definition itself, without regard to name or aliases. May return
 * {@code null} if problems occurred during the parsing of the bean definition.
 */
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
    Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

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

    try {
        // 建立用於承載屬性的AbstractBeanDefinition型別的GenericBeanDefinition物件,建立隨後立刻儲存class和parent屬性
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 硬編碼解析預設bean的各種屬性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

        // 設定描述 內容來自description子元素
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        // 解析元資料<meta key="" value="">
        parseMetaElements(ele, bd);
        // 解析lookup-method屬性
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 解析replace-method屬性
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        // 解析建構函式引數
        parseConstructorArgElements(ele, bd);
        // 解析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;
}

可以看到,有關 bean 的其他所有屬性的解析都在該函式中完成了。開始我們可以看到直接解析 classparent 屬性。然後建立了用於承載屬性的 AbstractBeanDefinition 型別的 GenericBeanDefinition 物件,建立隨後立刻儲存 classparent 屬性。

① 建立用於承載屬性的BeanDefinition

BeanDefinition 本身是一個介面,Spring 中提供了三種相關的實現類,如下圖所示。三種實現類均繼承自該介面的抽象實現類 AbstractBeanDefinitionBeanDefinition 是配置檔案 <bean> 元素標籤在容器中的內部表示形式。 <bean>元素標籤擁有的classscopelazy-init 等配置屬性對應 BeanDefinition 也有相應的 beanClassscopelazyInit 等屬性進行一一對應。

BeanDefinition及其實現類

其中,RootBeanDefinition 是最常用的實現類,它對應一般性的元素標籤;GenericBeanDefinition 是自2.5版本以後新加入的 bean 檔案配置屬性定義類,是一站式服務類。在配置檔案中用 parent 屬性可以定義父 <bean> 和子 <bean> ,父 <bean>RootBeanDefinition表示,而子 <bean>ChildBeanDefiniton 表示,而沒有父 <bean><bean> 就使用 RootBeanDefinition 表示。AbstractBeanDefinition 是對兩者共同的類資訊進行抽象。

Spring 通過 BeanDefinition配置檔案中的配置資訊轉換為容器的內部表示,並將這些 BeanDefinition 註冊到 BeanDefinitionRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置資訊的記憶體資料庫,主要是以 map 的形式儲存,後續操作直接從 BeanDefinitionRegistry 中讀取配置資訊。

因此,要想解析儲存 bean 的屬性資訊,需要先建立 BeanDefinition 的例項。程式碼中實際呼叫 createBeanDefinition 方法建立了 GenericBeanDefinition 型別的例項來儲存屬性資訊。

/**基於class和parent建立一個bean definition
 * Create a bean definition for the given class name and parent name.
 * @param className the name of the bean class
 * @param parentName the name of the bean's parent bean
 * @return the newly created bean definition
 * @throws ClassNotFoundException if bean class resolution was attempted but failed
 */
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
    throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(
        parentName, className, this.readerContext.getBeanClassLoader());
}

實際則又委託 BeanDefinitionReaderUtils 去進行建立:

/**
 * Create a new GenericBeanDefinition for the given parent name and class name,
 * eagerly loading the bean class if a ClassLoader has been specified.
 * @param parentName the name of the parent bean, if any
 * @param className the name of the bean class, if any
 * @param classLoader the ClassLoader to use for loading bean classes
 * (can be {@code null} to just register bean classes by name)
 * @return the bean definition
 * @throws ClassNotFoundException if the bean class could not be loaded
 */
public static AbstractBeanDefinition createBeanDefinition(
    @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

    // 建立GenericBeanDefinition例項
    GenericBeanDefinition bd = new GenericBeanDefinition();
    // 設定bd的parentName(bean標籤的parent屬性可能為空)
    bd.setParentName(parentName);

    if (className != null) {
        if (classLoader != null) {
            // 如果classLoader不為空,則使用以傳入的classLoader同一虛擬機器載入類物件,否則只是記錄className
            bd.setBeanClass(ClassUtils.forName(className, classLoader));
        }
        else {
            bd.setBeanClassName(className);
        }
    }
    return bd;
}

② 進一步解析各種屬性

建立完 bean 資訊的承載例項後,便可以進行各種 bean 配置屬性的解析了。先進入硬編碼解析預設bean的各種屬性的方法 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd)

/**
 * 硬編碼解析預設bean的各種屬性,返回儲存了配置資訊的bd
 */
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
                                                            @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

    // 判斷是否含有singleton屬性,新版本要用scope屬性
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    } else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    // 設定abstract屬性
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    // 設定lazy-init屬性,預設default為true
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(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 ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(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);
    }

    // 設定destory-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;
}

可以很清晰的看到,Spring 對各種 bean 屬性的解析,並將解析的屬性資訊都 setGenericBeanDefinition 物件中進行返回。這樣對於 <bean> 標籤的各種屬性全部解析完畢,下面需要處理 <bean> 標籤的子元素。

③ 解析子元素description

沒什麼好說的,程式碼如下:

// 設定描述 內容來自description子元素
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

/**
 * Utility method that returns the first child element value identified by its name.
 * @param ele the DOM element to analyze
 * @param childEleName the child element name to look for
 * @return the extracted text value, or {@code null} if no child element found
 */
@Nullable
public static String getChildElementValueByTagName(Element ele, String childEleName) {
    Element child = getChildElementByTagName(ele, childEleName);
    return (child != null ? getTextValue(child) : null);
}

④ 解析子元素meta

<meta> 屬性的使用如下:

<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
    <meta key="testStr" value="test meta" />
</bean>

這段配置並不會出現在 myTestBean 的屬性之中,只是一個額外的宣告。Spring 將其解析儲存至 BeanDefinitionattribute 中,所以可以利用 getAttribute(key) 來獲取。解析方法如下:

/**
 * 解析子元素meta
 * @param ele
 * @param attributeAccessor
 */
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); // 提取key
            String value = metaElement.getAttribute(VALUE_ATTRIBUTE); // 提取value
            // 將每個meta資料封裝為BeanMetadataAttribute物件
            BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
            attribute.setSource(extractSource(metaElement));
            // 儲存至bd
            attributeAccessor.addMetadataAttribute(attribute);
        }
    }
}

⑤ 解析子元素lookup-method

子元素 <lookup-method> 不是非常常用,關於該標籤的使用參考文章。解析該標籤的原始碼如下,與 parseMetaElements 類似,區別在於封裝資訊的類不同。

/**
 * 解析子元素lookup-method
 * Parse lookup-override sub-elements of the given bean element.
 */
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        // 只有lookup-method子元素進行處理
        if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
            Element ele = (Element) node;
            String methodName = ele.getAttribute(NAME_ATTRIBUTE); // name屬性
            String beanRef = ele.getAttribute(BEAN_ELEMENT); // bean屬性
            // 建立LookupOverride物件儲存資訊
            LookupOverride override = new LookupOverride(methodName, beanRef);
            override.setSource(extractSource(ele));
            // 新增至bd的MethodOverrides屬性物件
            overrides.addOverride(override);
        }
    }
}

⑥ 解析子元素replace-method

同樣,<replace-method> 標籤的用法參考文章。類似子元素 <lookup-method> 的資訊提取,解析該元素程式碼如下:

/**
 * 解析子元素replaced-method
 * Parse replaced-method sub-elements of the given bean element.
 */
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        // 只有replaced-method子元素進行處理
        if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
            Element replacedMethodEle = (Element) node;
            String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); // name屬性
            String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); // replacer屬性
            // 建立ReplaceOverride物件儲存資訊
            ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
            // 處理replaced-method的子元素arg-type
            List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
            for (Element argTypeEle : argTypeEles) {
                String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); // match屬性
                match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                if (StringUtils.hasText(match)) {
                    replaceOverride.addTypeIdentifier(match);
                }
            }
            replaceOverride.setSource(extractSource(replacedMethodEle));
            // 新增至bd的MethodOverrides屬性物件
            overrides.addOverride(replaceOverride);
        }
    }
}

⑦ 解析子元素constructor-arg

對建構函式子元素的解析在Spring的配置檔案中還是較常用的,原始碼如下:

/**
 * 解析子元素constructor-arg
 * Parse constructor-arg sub-elements of the given bean element.
 */
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
    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);
        }
    }
}

在配置檔案中 <bean> 元素標籤內可能包含多個 <constructor-arg> 子元素,所以遍歷全部子元素,當是 <constructor-arg> 時,呼叫 parseConstructorArgElement((Element) node, bd) 方法對每一個元素進行解析。

/**
 * 解析子元素constructor-arg
 * Parse a constructor-arg element.
 */
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
    String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); // index屬性
    String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); // type屬性
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // name屬性
    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));
                    // 解析ele也就是constructor-arg對應的屬性元素
                    Object value = parsePropertyValue(ele, bd, null);
                    // 建立ValueHolder物件儲存constructor-arg資訊
                    ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
                    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 {
                        // 儲存資訊至bd的constructorArgumentValues
                        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());
            Object value = parsePropertyValue(ele, bd, null);
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
            if (StringUtils.hasLength(typeAttr)) {
                valueHolder.setType(typeAttr);
            }
            if (StringUtils.hasLength(nameAttr)) {
                valueHolder.setName(nameAttr);
            }
            valueHolder.setSource(extractSource(ele));
            bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
        }
        finally {
            this.parseState.pop();
        }
    }
}

首先,提取 <constructor-arg> 標籤的必要屬性 indextypename,然後利用 parsePropertyValue(ele, bd, null) 方法解析 <constructor-arg> 子元素,再用 ConstructorArgumentValues.ValueHolder 型別物件封裝解析出的資訊。如果配置了 index 屬性,則會將 valueHolder 通過 bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder) 方法儲存至 indexedArgumentValues 屬性中,而如果沒有 index 屬性,則會通過 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder) 儲存至 genericArgumentValues 屬性中。是否擁有 index 屬性,決定了解析出的資訊儲存在 BeanDefinition 的哪個屬性中。繼續檢視其中 Spring 如何解析出 <constructor-arg> 所有資訊的。

/**
 * 解析ele對應的屬性元素
 * Get the value of a property element. May be a list etc.
 * Also used for constructor arguments, "propertyName" being null in this case.
 */
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
    // 如果propertyName不為空則是解析<property>,否則是<constructor-arg>
    String elementName = (propertyName != null ?
                          "<property> element for property '" + propertyName + "'" :
                          "<constructor-arg> element");

    // 一個屬性只能對應一種型別:ref, value, list等
    NodeList nl = ele.getChildNodes();
    Element subElement = null;
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        // 對應description或者meta不包含在內
        if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
            !nodeNameEquals(node, META_ELEMENT)) {
            // Child element is what we're looking for.
            if (subElement != null) {
                error(elementName + " must not contain more than one sub-element", ele);
            }
            else {
                subElement = (Element) node;
            }
        }
    }

    // 解析ref和value屬性
    boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
    boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);

    // 不能出現以下兩種情形:
    //    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進行判空
        String refName = ele.getAttribute(REF_ATTRIBUTE);
        if (!StringUtils.hasText(refName)) {
            error(elementName + " contains empty 'ref' attribute", ele);
        }
        // 用RuntimeBeanReference物件包裝ref屬性值
        RuntimeBeanReference ref = new RuntimeBeanReference(refName);
        ref.setSource(extractSource(ele));
        return ref;
    }
    else if (hasValueAttribute) {
        // 用TypedStringValue物件包裝value屬性值
        TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
        valueHolder.setSource(extractSource(ele));
        return valueHolder;
    }
    else if (subElement != null) {
        // 解析子元素
        return parsePropertySubElement(subElement, bd);
    }
    else {
        // 三者都沒有則會報錯
        error(elementName + " must specify a ref or value", ele);
        return null;
    }
}

程式碼內容上,對建構函式中屬性元素的解析,經歷了以下過程:

  • 略過 description 或者 meta
  • 提取解析 refvalue 屬性;
  • RuntimeBeanReference 物件包裝 ref 屬性值,或者用 TypedStringValue 物件包裝 value 屬性值或者解析子元素。

而對於子元素的處理,例如在建構函式中又嵌入了子元素 map

<constructor-arg>
    <map>
        <entry key="key" value="value" />
    </map>
</constructor-arg>

Spring 對於 <constructor-arg> 標籤的子元素單獨封裝了 parsePropertySubElement(subElement, bd) 方法對各種子元素的分類處理:

/**
 * Parse a value, ref or collection sub-element of a property or
 * constructor-arg element.
 * @param ele subelement of property element; we don't know which yet
 * @param defaultValueType the default type (class name) for any
 * {@code <value>} tag that might be created
 */
@Nullable
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)) {
            // 解析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;
    }
}

可以看到,在上面的函式中實現了所有可以支援的子類的分類處理,到這裡,我們已經大致理清建構函式的解析流程。

⑧ 解析子元素property

parsePropertyElements 函式完成子元素 <property> 的解析, <property> 的使用方式如下:

<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
    <property name="testStr" value="haha" />
</bean>

或者

<bean id="myTestBean">
    <property name="testStr">
        <list>
            <value>aa</value>
            <value>bb</value>
        </list>
    </property>
</bean>

具體的解析過程如下:

/**
 * 解析子元素property
 * Parse property sub-elements of the given bean element.
 */
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    // 提取所有的property子元素 呼叫parsePropertyElement處理
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
            parsePropertyElement((Element) node, bd);
        }
    }
}

提取所有的 property 子元素呼叫 parsePropertyElement((Element) node, bd) 處理:

/**
 * Parse a property element.
 */
public void parsePropertyElement(Element ele, BeanDefinition bd) {
    // 獲取property元素name屬性值
    String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
    if (!StringUtils.hasLength(propertyName)) {
        error("Tag 'property' must have a 'name' attribute", ele);
        return;
    }
    this.parseState.push(new PropertyEntry(propertyName));
    try {
        // 不允許多次對同一屬性配置
        if (bd.getPropertyValues().contains(propertyName)) {
            error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
            return;
        }
        Object val = parsePropertyValue(ele, bd, propertyName);
        PropertyValue pv = new PropertyValue(propertyName, val);
        parseMetaElements(ele, pv);
        pv.setSource(extractSource(ele));
        bd.getPropertyValues().addPropertyValue(pv);
    }
    finally {
        this.parseState.pop();
    }
}

先獲取 <property> 標籤的 name,然後同樣通過 parsePropertyValue(ele, bd, propertyName) 方法對其它屬性進行解析,該方法在解析 <constructor-arg> 時也曾經用過,並且方法通過呼叫時是否傳入 propertyName 引數決定解析的是 <property> 標籤還是 <constructor-arg> 標籤。最後通過 PropertyValue 進行封裝,並記錄在 BeanDefinition 中的 propertyValues 屬性中。

⑨ 解析子元素qualifier

對於 <qualifier> 標籤,其相關使用這裡再給出一篇文章。在Spring自動注入的時候,Spring容器匹配的候選 Bean 數目必須有且只有一個。當找不到一個匹配的 Bean 時,Spring容器會丟擲 BeanCreationException 異常,並指出必須至少擁有一個匹配的 Bean

<qualifier> 子元素的使用如下:

<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
    <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="qf" />
</bean>

或者:

<qualifier type="org.springframework.beans.factory.annotation.Qualifier">
    <attribute key="key1" value="value1"/>
    <attribute key="key2" value="value2"/>
</qualifier>

對於該標籤的解析,Spring利用 parseQualifierElements 方法進行解析。

/**
 * Parse qualifier sub-elements of the given bean element.
 */
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
            parseQualifierElement((Element) node, bd);
        }
    }
}

同樣,提取所有的 qualifier 子元素,呼叫 parseQualifierElement((Element) node, bd) 處理所有的標籤屬性。程式碼中分別對上述兩種格式的 <qualifier> 進行解析。

/**
 * Parse a qualifier element.
 */
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
    // 獲取type
    String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
    if (!StringUtils.hasLength(typeName)) {
        error("Tag 'qualifier' must have a 'type' attribute", ele);
        return;
    }
    this.parseState.push(new QualifierEntry(typeName));
    try {
        // 利用AutowireCandidateQualifier包裝typeName
        A