zoukankan      html  css  js  c++  java
  • Spring5源码分析(016)——IoC篇之解析bean标签:constructor-arg、property、qualifier

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总

    关于相关标签的使用和说明,建议参考最正宗的来源——官方参考文档:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html

    例如关于本文即将价绍的 constructor-arg : https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-constructor-injection

    注:文档内容比较多,建议按照关键字进行快速搜索。


      bean 标签的属性解析完之后,接下来需要解析的就是各种子元素了,从代码中可以看出,总共有 6 类子元素,分别是 :meta、lookup-method、replaced-method、constructor-arg、property、qualifier。

      上一篇已经对前面 3 个子元素的解析进行了分析。本文将对剩下的 3 个子元素的解析进行分析,这3个子元素的作用如下(具体示例和说明可参考后文的分析):

    • <constructor-arg/> :通过构造函数对 bean 进行内部属性设置。
    • <property/> :bean 属性直接设置。
    • <qualifier/> :指定引用 bean 的id(唯一性),消除引用歧义。

    本文目录结构如下:

    1、解析 constructor-arg 子元素

      constructor-arg ,构造函数参数,用于初始化指定的内部属性。

    1.1、constructor-arg 使用示例

      构造函数子元素的使用相对来说应该会多点,其解析相对来说也比较复杂。在开始分析前,先贴上官网上的一些 XML 配置示例 (来源: https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-constructor-injection )进行回顾: 

    <!-- 使用 ref 匹配 -->
    <beans>
        <bean id="beanOne" class="x.y.ThingOne">
            <constructor-arg ref="beanTwo"/>
            <constructor-arg ref="beanThree"/>
        </bean>
    
        <bean id="beanTwo" class="x.y.ThingTwo"/>
    
        <bean id="beanThree" class="x.y.ThingThree"/>
    </beans>
    
    <!-- 使用类型匹配 -->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg type="int" value="7500000"/>
        <constructor-arg type="java.lang.String" value="42"/>
    </bean>
    
    <!-- ------------------------------------------- -->
    <!-- 使用 index 匹配 -->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>
    
    <!-- ------------------------------------------- -->
    <!-- 使用 name 匹配 -->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>

       上面的配置是 Spring 构造函数配置中比较基础的集中配置,实现的功能就是对 beanOne / exampleBean 自动寻找对应的构造函数,并在初始化的时候将设置的函数传入进去。接下来我们来看下 constructor-arg 的具体解析过程。

    1.2、parseConstructorArgElements

      Spring 通过调用 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseConstructorArgElements(Element beanEle, BeanDefinition bd) 方法,完成对 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);
            // 提取 constructor-arg 标签
            if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
                // 解析 constructor-arg 元素
                parseConstructorArgElement((Element) node, bd);
            }
        }
    }

       这里也是遍历所有子节点元素,如果是 constructor-arg 标签,如委托 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseConstructorArgElement(Element ele, BeanDefinition bd) 进行实际的解析。

    1.3、parseConstructorArgElement

      实际对 constructor-arg 子元素进行解析的是 parseConstructorArgElement(Element ele, BeanDefinition bd) ,代码如下:

    /**
     * Parse a constructor-arg element.
     * <p>解析 constructor-arg 元素
     */
    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 {
                // 1.1、检查 index 属性构造函数下标索引不能小于 0
                int index = Integer.parseInt(indexAttr);
                if (index < 0) {
                    error("'index' cannot be lower than 0", ele);
                }
                else {
                    try {
                        // 1.2、将 index 封装到 ConstructorArgumentEntry 对象并添加到 ParseState 队列中
                        this.parseState.push(new ConstructorArgumentEntry(index));
                        // 1.3、解析 ele 对应的属性值
                        Object value = parsePropertyValue(ele, bd, null);
                        // 1.4、根据解析的结果值构造一个 ValueHolder 对象并设置对应的 type 、 name 、 source
                        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 {
                            // 1.5、添加到 indexedArgumentValue 中
                            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 {
            try {
                // 2.1、构建无参的 ConstructorArgumentEntry 对象并添加到 ParseState 队列中
                this.parseState.push(new ConstructorArgumentEntry());
                // 2.2、解析 ele 对应的属性值
                Object value = parsePropertyValue(ele, bd, null);
                // 2.3、根据解析的结果值构造一个 ValueHolder 对象并设置对应的 type 、 name 、 source
                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));
                // 2.4、添加到 genericArgumentValue 中
                bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
            }
            finally {
                this.parseState.pop();
            }
        }
    }

    这里首先获取 index 、 type 、 name 这3个属性值,然后根据是否有 index 来区分,进行后续的解析。具体逻辑其实差别不大。先看下有 index 场景下的解析过程:

    • 1.1、检查 index 属性构造函数下标索引不能小于 0。
    • 1.2、将 index 封装到 ConstructorArgumentEntry 对象并添加到 ParseState 队列中。
    • 1.3、通过调用 parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) 解析 ele 对应的属性值,也即是设置的具体结果值。
    • 1.4、根据解析的结果值构造一个 ValueHolder 对象并设置对应的 type 、 name 、 source。
    • 1.5、添加到 indexedArgumentValue 中。

    没有的 index 的解析过程其实类似:

    • 2.1、构建无参的 ConstructorArgumentEntry 对象并添加到 ParseState 队列中。
    • 1.3、通过调用 parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) 解析 ele 对应的属性值,也即是设置的具体结果值。
    • 2.3、根据解析的结果值构造一个 ValueHolder 对象并设置对应的 type 、 name 、 source。
    • 2.4、添加到 genericArgumentValue 中。

      可以看出,差别仅在于 ConstructorArgumentEntry 有无构造器参数,最后是添加到 genericArgumentValue 集合中。

    1.4、parsePropertyValue

      解析 ele 对应的属性值是通过调用 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) 来实现的,最后返回的是对应的结果值,代码如下: 

    /**
     * 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) {
        String elementName = (propertyName != null ?
                "<property> element for property '" + propertyName + "'" :
                "<constructor-arg> element");
    
        // 1、一个属性只能对应一种类型: ref, value, list 等
        // Should only have one child element: ref, value, list, etc.
        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;
                }
            }
        }
        // 2.1、是否有 constructor-arg 的 ref 属性
        boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        // 2.2、是否有 constructor-arg 的 value 属性
        boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        if ((hasRefAttribute && hasValueAttribute) ||
                ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
            /**
             * 2.3、在 constructor-arg 中不存在:
             *     1、同时有 ref 属性 和 value 属性
             *     2、存在 ref 属性 或者 value 属性,且又有子元素
             */
            error(elementName +
                    " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
        }
    
        if (hasRefAttribute) {
            // 3、ref 属性处理,这是 bean 引用,使用 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) {
            // 4、value 属性处理,使用 TypedStringValue 封装
            TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
            valueHolder.setSource(extractSource(ele));
            return valueHolder;
        }
        else if (subElement != null) {
            // 5、解析子元素
            return parsePropertySubElement(subElement, bd);
        }
        else {
            // 既没有 ref 也没有 value 也没有子元素,无法解析,返回 null
            // Neither child element nor "ref" or "value" attribute found.
            error(elementName + " must specify a ref or value", ele);
            return null;
        }
    }

       这里主要分为5个步骤:

    • 1、主要是判断一个属性只能对应一种类型: ref, value, list 等。
    • 2、关于 constructor-arg 的配置,不允许以下这2种情况:
      • 2.1、同时有 ref 属性 和 value 属性
      • 2.2、存在 ref 属性 或者 value 属性,且又有子元素
    • 3、ref 属性处理,这是 bean 引用,使用 RuntimeBeanReference 封装对应的 ref 名称
    • 4、value 属性处理,使用 TypedStringValue 封装
    • 5、解析子元素,这里调用的是 parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) 方法。

    1.5、parsePropertySubElement

      对于 constructor-arg 子元素种嵌套的子元素的解析,调用的是 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) 来进行处理,其内部则委托 parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) 进行进一步的解析: 

    /**
     * Parse a value, ref or collection sub-element of a property or
     * constructor-arg element.
     * <p>解析属性或构造函数元素的值、 ref 或集合子元素
     * @param ele subelement of property element; we don't know which yet
     * @param bd the current bean definition (if any)
     */
    @Nullable
    public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
        return parsePropertySubElement(ele, bd, null);
    }
    
    /**
     * 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 bd the current bean definition (if any)
     * @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.
            // 解析 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;
        }
        else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
            // 解析 array 子元素
            return parseArrayElement(ele, bd);
        }
        else if (nodeNameEquals(ele, LIST_ELEMENT)) {
            // 解析 list 子元素
            return parseListElement(ele, bd);
        }
        else if (nodeNameEquals(ele, SET_ELEMENT)) {
            // 解析 set 子元素
            return parseSetElement(ele, bd);
        }
        else if (nodeNameEquals(ele, MAP_ELEMENT)) {
            // 解析 map 子元素
            return parseMapElement(ele, bd);
        }
        else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
            // 解析 props 子元素
            return parsePropsElement(ele);
        }
        else {
            error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
            return null;
        }
    }

       可以看到,在上面的函数种实现了所有可支持的子类的分类处理。到之类,我们已经大致理清构造函数的解析过程,至于更深入的解析,读者有兴趣的可以自己进行探索。

    2、解析 property 子元素

      property 子元素其实就是 bean 内部的属性设置。

    2.1、property 使用示例

      很常见的一个配置就是数据库的配置,官网例子来源:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-factory-properties-detailed : 

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!-- results in a setDriverClassName(String) call -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="masterkaoli"/>
    </bean>

      

    2.2、parsePropertyElements

      property 子元素的解析,调用的是 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parsePropertyElements(Element beanEle, BeanDefinition bd) ,没错,也只是遍历子节点,然后再委托 parsePropertyElement(Element ele, BeanDefinition bd) 进行处理:

    /**
     * Parse property sub-elements of the given bean element.
     */
    public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
        NodeList nl = beanEle.getChildNodes();
        // 遍历子节点
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            // 提取 property 子元素
            if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
                parsePropertyElement((Element) node, bd);
            }
        }
    }

    2.3、parsePropertyElement

      真正干活进行解析的是 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parsePropertyElement(Element ele, BeanDefinition bd) ,代码如下:

    /**
     * Parse a property element.
     */
    public void parsePropertyElement(Element ele, BeanDefinition bd) {
        // 获取 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 {
            // 不允许存在相同的 name
            if (bd.getPropertyValues().contains(propertyName)) {
                error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
                return;
            }
            // 解析属性值
            Object val = parsePropertyValue(ele, bd, propertyName);
            // 创建 PropertyValue 对象
            PropertyValue pv = new PropertyValue(propertyName, val);
            // 解析 meta 子元素,可能会把 property 配置同时设置为 meta
            parseMetaElements(ele, pv);
            pv.setSource(extractSource(ele));
            bd.getPropertyValues().addPropertyValue(pv);
        }
        finally {
            this.parseState.pop();
        }
    }
    • 调用 parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) 方法,解析对应的属性值
    • 通过解析后的属性值创建 PropertyValue 对象;
    • 如果有设置 meta ,则进行解析
    • 最后将 PropertyValue 对象添加到 MutablePropertyValues 中

    3、解析 qualifier 子元素

      对于 qualifier 元素的获取,我们接触更多的是注解的形式。在使用 Spring 框架进行自动注入时, Spring 容器种匹配的候选 bean 数目必须有且仅有1个,即不能没有,也不能多余1个。当有多个相同类型的 bean 时,仅通过类型时无法消除歧义的,这个时候就需要 qualifier 来指定注入的 bean 名称,以便消除歧义,具体配置可参考 https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-autowired-annotation-qualifiers

      解析过程如下:

    /**
     * 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);
            // 提取 qualifier 子元素
            if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
                parseQualifierElement((Element) node, bd);
            }
        }
    }
    /**
     * 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 对象
            AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
            qualifier.setSource(extractSource(ele));
            // 提取 value 属性,并设置到 AutowireCandidateQualifier 对象中
            String value = ele.getAttribute(VALUE_ATTRIBUTE);
            if (StringUtils.hasLength(value)) {
                qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
            }
            NodeList nl = ele.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                // attribute 标签
                if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
                    Element attributeEle = (Element) node;
                    // attribute 标签的 key 属性 和 value 属性
                    String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
                    String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
                    if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
                        // 根据 key 和 value 创建 BeanMetadataAttribute 对象
                        BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
                        attribute.setSource(extractSource(attributeEle));
                        // 添加到 metadataAttribute 中
                        qualifier.addMetadataAttribute(attribute);
                    }
                    else {
                        error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
                        return;
                    }
                }
            }
            // 添加到 qualifiers 中
            bd.addQualifier(qualifier);
        }
        finally {
            this.parseState.pop();
        }
    }

       解析过程也大同小异,此不赘述。

    4、总结

      本文主要是对 bean 标签的3个子元素 constructor-arg、property、qualifier 的使用和解析进行了简要的说明和分析。据笔者经验,6个子元素中,用的最多的是 constructor-arg、property ,尤其是 property 子元素,几乎所有的 工程都要用到,像数据库配置、redis 和 MQ 等的配置。

    5、参考

  • 相关阅读:
    redis set
    实现排行榜神器——redis zset
    nginx挂了怎么办
    django 400报错
    项目管理【18】 | 项目进度管理-规划进度管理
    项目管理【17】 | 项目进度管理-进度管理概述
    项目管理【23】 | 项目进度管理-制定进度计划
    移动端开发基础【7】生命周期
    移动端开发案例【7】移动端朋友圈发布显示开发
    移动端开发案例【6】移动端群、用户搜索开发
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13551316.html
Copyright © 2011-2022 走看看