constructor-arg子元素的解析

Posted wcj-java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了constructor-arg子元素的解析相关的知识,希望对你有一定的参考价值。

  对构造函数的解析是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置配置都不陌生,举一个简单的例子来开始对constructor-arg的解析。

1.   User.java

public class User implements Serializable{
       /**
        * 注释内容
        */
       private static final long serialVersionUID = -6199935307945147019L;
       private String name;
       private int age;
       public User(String name, int age)
       {
              this.name = name;
              this.age = age;
       }
       @Override
       public String toString() {
              return "User [name=" + name + ", age=" + age + "]";
       }
}

 2.   xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
    <bean id="user" class="com.example.demo.entity.User">
        <constructor-arg index="0">
            <value>zzzzzz</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>14</value>
        </constructor-arg>
    </bean>
</beans>

 上面的配置是spring构造函数的基础配置。我们来看看具体的xml解析过程。

对于constructor-arg子元素的解析,Spring是通过parseConstructorArgElements函数来实现的,具体的代码如下:   

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)) {
               parseConstructorArgElement((Element) node, bd);
           }
        }
    }

 这个结构似乎可以想象的到,遍历所有子元素,也就是提取所有constructor-arg然后进行解析,但是具体的解析却被放置在了另一个函数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));
                       // 解析ele对应的属性元素
                       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));
                       // 不允许重复指定相同参数
                       if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                           error("Ambiguous constructor-arg entries for index " + index, ele);
                       }
                       else {
                       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上必要的属性(index、type、name)。

如果配置中指定了index属性,那么操作步骤如下。

(1)     解析constructor-arg的子元素。

(2)     使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。

(3)     将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的IndexedArgumentValues属性中。

如果没有指定index属性,那么操作步骤如下:

(1)     解析constructor-arg的子元素。

(2)     使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。

(3)     将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中。

可以看到,对于是否指定index属性来讲,Spring的处理流程是不同的,关键在于属性信息被保存的位置。

了解了整个流程后,我们进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:  

  public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
        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;
               }
           }
        }
        // 解析constructor-arg上的ref属性。
        boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        // 解析constructor-arg上的value属性
        boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        if ((hasRefAttribute && hasValueAttribute) ||
               ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
           // 在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) {
           // 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也没有子元素,Spring就会报错.
           error(elementName + " must specify a ref or value", ele);
           return null;
        }
    }

 从代码上来看,对构造函数中属性元素的解析,经理了以下几个过程。

(1)   略过description或者meta。

(2)   提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg上不存在以下情况。

同时既有ref属性又有value属性。

存在ref属性或者value属性且又有子元素

(3)   Ref属性的处理。使用RuntimeBeanReference封装对应的ref名称。如:

<constructor-arg ref=“a” >

(4)   value属性的处理。使用TypedStringValue封装,例如:

<constructor-arg value=“a” >

(5)   子元素的处理。例如:

<constructor-arg>

  <map>

<entry key=”key” value=”value” />

</map>

</ constructor-arg>

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);
        }
        else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
           BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
           if (nestedBd != null) {
               nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
           }
           return nestedBd;
        }
        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.
               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;
        }
    }

 可以看到,在上面的函数中实现了所有可支持的子类的分类处理。

 

 

 

以上是关于constructor-arg子元素的解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring - 属性“name”不允许出现在元素“constructor-arg”中

共享元素转换在父片段和子片段之间不起作用(嵌套片段)

从流输入中解析没有根元素的 XML 片段列表

cvc-complex-type.3.2.2: 元素 'constructor-arg' 中不允许出现属性 'name'

jquery中的$的特殊用法

Spring中constructor-arg的value属性是啥意思