3.6 spring-construction-arg 子元素的使用与解析

杜俊楚
2023-12-01

  对于构造函数子元素是非常常用的. 相信大家也一定不陌生,

举个小例子:

  

 1 public class Animal {
 2 
 3     public String type;
 4 
 5     public int age;
 6 
 7     /**
 8      * @param type
 9      * @param age
10      */
11     public Animal(String type, int age) {
12         super();
13         this.type = type;
14         this.age = age;
15     }
16 
17     /*
18      * (non-Javadoc)
19      * 
20      * @see java.lang.Object#toString()
21      */
22     @Override
23     public String toString() {
24         return "Animal [type=" + type + ", age=" + age + "]";
25     }
26 }

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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="animal" class="test.constructor.Animal">
        <constructor-arg index="0" value="cat" type="String"></constructor-arg>
        <constructor-arg value="100" ></constructor-arg>
    </bean>
</beans>

 

测试类如下:

 1 public class Main {
 2 
 3     public static String XML_PATH = "test\\constructor\\applicationContxt.xml";
 4 
 5     public static void main(String[] args) {
 6         try {
 7             Resource resource = new ClassPathResource(XML_PATH);
 8             XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
 9             Animal bean = (Animal) beanFactory.getBean("animal");
10             System.out.println(bean);
11         }
12         catch (Exception e) {
13             e.printStackTrace();
14         }
15     }
16 }

  对于construction-arg 子元素的解析,Spring 是通过 BeanDefinitionParserDelegate. parseConstructorArgElements(Element beanEle, BeanDefinition bd); 方法来实现的,

具体代码如下,

 1 /**
 2      * Parse constructor-arg sub-elements of the given bean element.
 3      */
 4     public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
 5         NodeList nl = beanEle.getChildNodes();
 6         for (int i = 0; i < nl.getLength(); i++) {
 7             Node node = nl.item(i);
 8             if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
 9                 // parseConstructorArgElement
10                 parseConstructorArgElement((Element) node, bd);
11             }
12         }
13     }

追踪下去如下:

 1 /**
 2      * Parse a constructor-arg element.
 3      */
 4     public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
 5         // 提取 Index
 6         String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
 7         // 提取 type
 8         String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
 9         // 提取 name
10         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
11         if (StringUtils.hasLength(indexAttr)) {
12             try {
13                 int index = Integer.parseInt(indexAttr);
14                 if (index < 0) {
15                     error("'index' cannot be lower than 0", ele);
16                 }
17                 else {
18                     try {
19                         this.parseState.push(new ConstructorArgumentEntry(index));
20                         // 对ele元素的解析
21                         Object value = parsePropertyValue(ele, bd, null);
22                         ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(
23                                 value);
24                         if (StringUtils.hasLength(typeAttr)) {
25                             valueHolder.setType(typeAttr);
26                         }
27                         if (StringUtils.hasLength(nameAttr)) {
28                             valueHolder.setName(nameAttr);
29                         }
30                         valueHolder.setSource(extractSource(ele));
31                         // 不允许重复指定相同的参数
32                         if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(
33                                 index)) {
34                             error("Ambiguous constructor-arg entries for index " + index,
35                                     ele);
36                         }
37                         else {
38                             // 对于有Index 的参数放这里
39                             bd.getConstructorArgumentValues().addIndexedArgumentValue(
40                                     index, valueHolder);
41                         }
42                     }
43                     finally {
44                         this.parseState.pop();
45                     }
46                 }
47             }
48             catch (NumberFormatException ex) {
49                 error("Attribute 'index' of tag 'constructor-arg' must be an integer",
50                         ele);
51             }
52         }
53         else {
54             // 如果没有index 属性则忽略去属性,自动寻找
55             try {
56                 this.parseState.push(new ConstructorArgumentEntry());
57                 Object value = parsePropertyValue(ele, bd, null);
58                 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(
59                         value);
60                 if (StringUtils.hasLength(typeAttr)) {
61                     valueHolder.setType(typeAttr);
62                 }
63                 if (StringUtils.hasLength(nameAttr)) {
64                     valueHolder.setName(nameAttr);
65                 }
66                 valueHolder.setSource(extractSource(ele));
67                 // 对于没Index参数的放这里
68                 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
69             }
70             finally {
71                 this.parseState.pop();
72             }
73         }
74     }

上面一段代码虽然长,但涉及的逻辑其实并不复杂.

1.首先是提取 constructor-arg 上的必要属性,(index,name,type);

 

如果是指定了index属性的 

  1,解析constructor-arg 的子元素

  2,使用 ConstructorArgumentValues.ValueHolder来封装解析出来的数据,

  3,将 index,type,name 一起封装在当前beandefinition 的 IndexedArgumentValues 属性当中

如果没有指定index属性的

  1,解析constructor-arg 的子元素

  2,使用 ConstructorArgumentValues.ValueHolder来封装解析出来的数据,

  3,将 index,type,name 一起封装在当前beandefinition 的 GenericArgumentValues 属性当中

这里可以看对于是否指定index 参数的,Spring的处理流程是不同的,关键在于保存的位置不同

 

那么了解这个过程之后,我们尝试进一步了解解析构造函数配置中解析子元素的过程,进入 parsePropertyValue

代码如下:

 1 /**
 2      * Get the value of a property element. May be a list etc. Also used for constructor
 3      * arguments, "propertyName" being null in this case.
 4      */
 5     /**
 6      * @param ele
 7      * @param bd
 8      * @param propertyName
 9      * @return
10      */
11     public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
12         String elementName = (propertyName != null) ? "<property> element for property '"
13                 + propertyName + "'" : "<constructor-arg> element";
14 
15         // Should only have one child element: ref, value, list, etc.
16         // 应该只有一个子元素:REF,值,列表等。
17         NodeList nl = ele.getChildNodes();
18         Element subElement = null;
19         for (int i = 0; i < nl.getLength(); i++) {
20             Node node = nl.item(i);
21             // 对应的description 或者meta不处理
22             if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)
23                     && !nodeNameEquals(node, META_ELEMENT)) {
24                 // Child element is what we're looking for.
25                 if (subElement != null) {
26                     error(elementName + " must not contain more than one sub-element",
27                             ele);
28                 }
29                 else {
30                     subElement = (Element) node;
31                 }
32             }
33         }
34         // 解析 ref
35         boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
36         // 解析 value
37         boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
38         if ((hasRefAttribute && hasValueAttribute)
39                 || ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
40             /*
41              * 1.不能同时有ref 又有 value 2.不能存在ref 或者 value 又有子元素
42              */
43             error(elementName
44                     + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element",
45                     ele);
46         }
47 
48         if (hasRefAttribute) {
49             String refName = ele.getAttribute(REF_ATTRIBUTE);
50             if (!StringUtils.hasText(refName)) {
51                 error(elementName + " contains empty 'ref' attribute", ele);
52             }
53             // ref 属性的处理 , 使用RuntimeBeanReference封装对应的ref名称
54             RuntimeBeanReference ref = new RuntimeBeanReference(refName);
55             ref.setSource(extractSource(ele));
56             return ref;
57         }
58         else if (hasValueAttribute) {
59             // Value 属性的处理 , 使用TypedStringValue封装对应的
60             TypedStringValue valueHolder = new TypedStringValue(
61                     ele.getAttribute(VALUE_ATTRIBUTE));
62             valueHolder.setSource(extractSource(ele));
63             return valueHolder;
64         }
65         else if (subElement != null) {
66             // 解析子元素
67             return parsePropertySubElement(subElement, bd);
68         }
69         else {
70             // Neither child element nor "ref" or "value" attribute found.
71             // 对于没有ref 也没有子元素的,Spring只好丢出异常
72             error(elementName + " must specify a ref or value", ele);
73             return null;
74         }
75     }

从上面的代码上看,对构造函数中属性的解析,经历了以下几个过程:

  1. 略过description 和 meta

  2. 提取 ref ,value 属性,并验证其合法性,

  3. ref 属性的处理,使用 RuntimeBeanReference 封装

  4. Value 属性的处理 , 使用TypedStringValue封装

  5. 子属性的处理 如 

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

  而对于子元素,则交给 parsePropertySubElement 方法来实现对各种子元素进行分类处理

代码如下:

1 public Object parsePropertySubElement(Element ele, BeanDefinition bd) {
2         return parsePropertySubElement(ele, bd, null);
3     }

继续如下:

 1 /**
 2      * Parse a value, ref or collection sub-element of a property or constructor-arg
 3      * element.
 4      * 
 5      * @param ele subelement of property element; we don't know which yet
 6      * @param defaultValueType the default type (class name) for any {@code &lt;value&gt;}
 7      *        tag that might be created
 8      */
 9     public Object parsePropertySubElement(Element ele, BeanDefinition bd,
10             String defaultValueType) {
11         if (!isDefaultNamespace(ele)) {
12             return parseNestedCustomElement(ele, bd);
13         }
14         else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
15             BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
16             if (nestedBd != null) {
17                 nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
18             }
19             return nestedBd;
20         }
21         else if (nodeNameEquals(ele, REF_ELEMENT)) {
22             // A generic reference to any name of any bean.
23             String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
24             boolean toParent = false;
25             if (!StringUtils.hasLength(refName)) {
26                 // A reference to the id of another bean in the same XML file.
27                 // 解析本地资源
28                 refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);
29                 if (!StringUtils.hasLength(refName)) {
30                     // A reference to the id of another bean in a parent context.
31                     // 解析父类资源
32                     refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
33                     toParent = true;
34                     if (!StringUtils.hasLength(refName)) {
35                         error("'bean', 'local' or 'parent' is required for <ref> element",
36                                 ele);
37                         return null;
38                     }
39                 }
40             }
41             if (!StringUtils.hasText(refName)) {
42                 error("<ref> element contains empty target attribute", ele);
43                 return null;
44             }
45             RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
46             ref.setSource(extractSource(ele));
47             return ref;
48         }
49         // 对idref 的处理
50         else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
51             return parseIdRefElement(ele);
52         }
53         // 对value 的处理
54         else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
55             return parseValueElement(ele, defaultValueType);
56         }
57         // 对 null 的处理
58         else if (nodeNameEquals(ele, NULL_ELEMENT)) {
59             // It's a distinguished null value. Let's wrap it in a TypedStringValue
60             // object in order to preserve the source location.
61             TypedStringValue nullHolder = new TypedStringValue(null);
62             nullHolder.setSource(extractSource(ele));
63             return nullHolder;
64         }
65         // 对array 的处理
66         else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
67             return parseArrayElement(ele, bd);
68         }
69         // 对list 的处理
70         else if (nodeNameEquals(ele, LIST_ELEMENT)) {
71             return parseListElement(ele, bd);
72         }
73         // 对set 的处理
74         else if (nodeNameEquals(ele, SET_ELEMENT)) {
75             return parseSetElement(ele, bd);
76         }
77         // 对map 的处理
78         else if (nodeNameEquals(ele, MAP_ELEMENT)) {
79             return parseMapElement(ele, bd);
80         }
81         // 对props 的处理
82         else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
83             return parsePropsElement(ele);
84         }
85         else {
86             error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
87             return null;
88         }
89     }

可以看到,上面方法中实现了所有可支持的子类的分类处理,到这里,我们已经大致清楚构造函数的处理流程了,至于在深入的解析,这里不做解析.

 

 

转载于:https://my.oschina.net/u/1590001/blog/268186

 类似资料: