bean子元素的解析

Posted joe-go

tags:

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

前言

  在上篇文章bean标签的解析中讲述了BeanDefinition已经完成了对bean标签属性的解析工作。在完成bean标签基本属性解析后,会依次调用parseMetaElements()、parseLookupOverrideSubElements()、parseReplacedMethodSubElements()等对子元素meta、lookup-method、replace-method等进行解析。下面分别对其说明解析过程。

meta子元素

在开始解析分析前,先来回顾一下meta属性的使用。

<bean id="car" class="test.CarFactoryBean">
  <meta key = "testMeta" value = "hello">
</bean>

这段代码不会体现在CarFactoryBean的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过BeanDefinition的getAttribute(key)方法进行获取。

在Spring中,对meta属性的解析代码如下:

 1 public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
 2         //获取当前节点所有子元素
 3         NodeList nl = ele.getChildNodes();
 4         for (int i = 0; i < nl.getLength(); i++) {
 5             Node node = nl.item(i);
 6             //提取meta
 7             if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
 8                 Element metaElement = (Element) node;
 9                 String key = metaElement.getAttribute(KEY_ATTRIBUTE);
10                 String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
11                 BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
12                 attribute.setSource(extractSource(metaElement));
13                 attributeAccessor.addMetadataAttribute(attribute);
14             }
15         }
16     }

解析过程较为简单,获取相应的BeanMetadataAttribute对象,然后通过addMetadataAttribute(attribute)加入到BeanMetadataAttributeAccessor中。如下:

public void addMetadataAttribute(BeanMetadataAttribute attribute) {
        super.setAttribute(attribute.getName(), attribute);
    }

委托给AttributeAccessorSupport类来实现:

public void setAttribute(String name, @Nullable Object value) {
        Assert.notNull(name, "Name must not be null");
        if (value != null) {
            this.attributes.put(name, value);
        }
        else {
            removeAttribute(name);
        }
    }

AttributeAccessorSupport是接口AttributeAccessor的实现者。AttributeAccessor接口定义了与其他对象的元数据进行连接和访问的约定,可以通过该接口对属性进行获取、设置、删除操作。

设置完元数据后,则可以通过getAttribute()获取元数据,如下:

public Object getAttribute(String name) {
        Assert.notNull(name, "Name must not be null");
        return this.attributes.get(name);
    }

lookup-method子元素

lookup-method子元素不是很常用,但是在某些时候它的确是非常有用的属性。通常我们称它为获取器注入:获取器注入是一种特殊的方式注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计某些可插拔的功能上,解除程序依赖。先来看一下具体的应用:

先声明一个类:

public interface Car {

    void display();
}

public class Bmw implements Car{
    @Override
    public void display() {
        System.out.println("我是 BMW");
    }
}

public class Hongqi implements Car{
    @Override
    public void display() {
        System.out.println("我是 hongqi");
    }
}

public abstract class Display {


    public void display(){
        getCar().display();
    }

    public abstract Car getCar();
}

   public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");

        Display display = (Display) context.getBean("display");
        display.display();
    }
}

Spring配置文件如下:

    <bean id="display" class="org.springframework.core.test1.Display">
        <lookup-method name="getCar" bean="hongqi"/>
    </bean>

运行结果:

我是 hongqi

如果将bean="hongqi"替换为bean="bmw",则运行结果为:

我是 BMW

到这里,我们已经初步了解了lookup-method子元素所提供的大致功能,下面就来看一下Spring中解析这个子元素的源码:

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //在Spring默认bean的子元素下且为<lookup-method时有效
            if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
                Element ele = (Element) node;
                //获取要修饰的方法
                String methodName = ele.getAttribute(NAME_ATTRIBUTE);
                //获取配置文件中返回的bean
                String beanRef = ele.getAttribute(BEAN_ELEMENT);
                LookupOverride override = new LookupOverride(methodName, beanRef);
                override.setSource(extractSource(ele));
                overrides.addOverride(override);
            }
        }
    }

与上一个子元素解析的过程基本相似,只是在数据存储上使用LookupOverride类型的实体类来进行数据承载并记录在 AbstractBeanDefinition中的methodOverride属性中。

replaced-method子元素

这个子元素的作用是对bean中replace-method的提取,称之为方法替换:可以在运行时用新的方法替换现有的方法。与之前的lookup-method不同的是replace-method不但可以动态的替换返回实体bean,而且还能动态的更改原有方法的逻辑。来看一下实例:

public class Method {
    public void display(){
        System.out.println("我是原始方法");
    }
}

public class MethodReplace implements MethodReplacer {

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("我是替换方法");

        return null;
    }
}

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");

        Method method = (Method) context.getBean("method");
        method.display();
    }

Spring配置文件如下:

  <bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/>
    
    <bean id="method" class="org.springframework.core.test1.Method"/>

运行结果:

我是原始方法

在Spring配置文件中增加replace-method子元素:

  <bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/>

    <bean id="method" class="org.springframework.core.test1.Method">
        <replaced-method name="display" replacer="methodReplace"/>
    </bean>

运行结果为:

我是替换方法

至此已经知道了,replaced-method 的用法,来看一下它的源码解析:

public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //在Spring默认bean的子元素下且为<replaced-method有效
            if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
                Element replacedMethodEle = (Element) node;
                //提取要替换的方法
                String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
                //提取对应的新的替换方法
                String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
                ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
                // Look for arg-type match elements.
                List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
                for (Element argTypeEle : argTypeEles) {
                    //记录参数
                    String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
                    match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                    if (StringUtils.hasText(match)) {
                        replaceOverride.addTypeIdentifier(match);
                    }
                }
                replaceOverride.setSource(extractSource(replacedMethodEle));
                overrides.addOverride(replaceOverride);
            }
        }
    }

从上面的代码可以看出,无论是lookup-method还是replaced-method都是构造了一个MethodOverride,并最终记录在了AbstractBeanDefinition中的methoOverrides属性中。

子元素constructor-arg

对构造函数的解析是非常常用的,同时也是非常复杂的,先来举个例子:

<beans>
<bean id="helloBean" class="com.joe.HelloBean">
   <constructor-arg index=‘0‘>
     <value>hello</value>
   </constructor-arg>
    <constructor-arg index=‘1‘>
     <value>joe</value>
   </constructor-arg>
    ......
</bean>
    
  .......
</beans>

上面的配置是Spring构造函数配置中最简单基础的配置,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去,现在我们来看看具体的Spring解析过程:

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);
            }
        }
    }

可以看出,parseConstructorArgElement()方法对constructor-arg进行解析的。具体代码:

 1 public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
 2         //提取index属性
 3         String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
 4         //提取type属性
 5         String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
 6         //提取name属性
 7         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 8         //判断配置文件中是否包含了index属性
 9         if (StringUtils.hasLength(indexAttr)) {
10             try {
11                 int index = Integer.parseInt(indexAttr);
12                 if (index < 0) {
13                     error("‘index‘ cannot be lower than 0", ele);
14                 }
15                 else {
16                     try {
17                         this.parseState.push(new ConstructorArgumentEntry(index));
18                         //解析ele对应的属性元素
19                         Object value = parsePropertyValue(ele, bd, null);
20                         ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
21                         if (StringUtils.hasLength(typeAttr)) {
22                             valueHolder.setType(typeAttr);
23                         }
24                         if (StringUtils.hasLength(nameAttr)) {
25                             valueHolder.setName(nameAttr);
26                         }
27                         valueHolder.setSource(extractSource(ele));
28                         //判断参数是否重复
29                         if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
30                             error("Ambiguous constructor-arg entries for index " + index, ele);
31                         }
32                         else {
33                             bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
34                         }
35                     }
36                     finally {
37                         this.parseState.pop();
38                     }
39                 }
40             }
41             catch (NumberFormatException ex) {
42                 error("Attribute ‘index‘ of tag ‘constructor-arg‘ must be an integer", ele);
43             }
44         }
45         else {
46             try {
47                 this.parseState.push(new ConstructorArgumentEntry());
48                 Object value = parsePropertyValue(ele, bd, null);
49                 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
50                 if (StringUtils.hasLength(typeAttr)) {
51                     valueHolder.setType(typeAttr);
52                 }
53                 if (StringUtils.hasLength(nameAttr)) {
54                     valueHolder.setName(nameAttr);
55                 }
56                 valueHolder.setSource(extractSource(ele));
57                 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
58             }
59             finally {
60                 this.parseState.pop();
61             }
62         }
63     }

 上面的代码很多,但是涉及的逻辑其实并不复杂:

(1)第2~7行:提取constructor-arg上必要的属性index、type、name。

(2)第9行:判断配置文件中是否指定了index属性。

(3)第10~44行:配置文件中指定了index属性。

  1.第19行:解析constructor-arg的子元素。

  2.第20行:使用ConstructorArgumentValues.ValueHolder 类型来封装解析出来的元素。

  3.第33行:将type、name、index属性一起封装到ConstructorArgumentValues.ValueHolder类型中并添加到当前BeanDefinition的ConstructorArgumentValues的indexedArgumentValues属性中。

(4)第46~60行:配置文件中没有指定index属性。与步骤3基本一致,只是没有对index属性进行封装和将属性封装的位置变了,没有index 属性是将其他属性封装在genericArgumentValues中。

有以上的逻辑可以看出,对于是否制定index属性来讲,Spring的处理流程是不一样的,关键是在于属性信息的保存位置不同。

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

 1 public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
 2         String elementName = (propertyName != null ?
 3                 "<property> element for property ‘" + propertyName + "‘" :
 4                 "<constructor-arg> element");
 5 
 6         // 一种属性只能对应一种类型:ref、value、list等
 7         NodeList nl = ele.getChildNodes();
 8         Element subElement = null;
 9         for (int i = 0; i < nl.getLength(); i++) {
10             Node node = nl.item(i);
11             //对应description或者meta不处理
12             if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
13                     !nodeNameEquals(node, META_ELEMENT)) {
14                 // Child element is what we‘re looking for.
15                 if (subElement != null) {
16                     error(elementName + " must not contain more than one sub-element", ele);
17                 }
18                 else {
19                     subElement = (Element) node;
20                 }
21             }
22         }
23 
24         //判断constructor-arg上是否含有ref属性
25         boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
26         //判断constructor-arg上是否含有value属性
27         boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
28         //不能同时存在ref属性和value属性;也不能存在ref属性或者value属性且又有子元素
29         if ((hasRefAttribute && hasValueAttribute) ||
30                 ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
31             error(elementName +
32                     " is only allowed to contain either ‘ref‘ attribute OR ‘value‘ attribute OR sub-element", ele);
33         }
34 
35         if (hasRefAttribute) {
36             //对ref属性进行处理,使用RuntimeBeanReference封装对应的ref名称
37             String refName = ele.getAttribute(REF_ATTRIBUTE);
38             if (!StringUtils.hasText(refName)) {
39                 error(elementName + " contains empty ‘ref‘ attribute", ele);
40             }
41             RuntimeBeanReference ref = new RuntimeBeanReference(refName);
42             ref.setSource(extractSource(ele));
43             return ref;
44         }
45         else if (hasValueAttribute) {
46             //对value属性的处理,使用TypedStringValue进行处理
47             TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
48             valueHolder.setSource(extractSource(ele));
49             return valueHolder;
50         }
51         else if (subElement != null) {
52             //解析子元素
53             return parsePropertySubElement(subElement, bd);
54         }
55         else {
56             // 既没有ref,value,也没有子元素。
57             error(elementName + " must specify a ref or value", ele);
58             return null;
59         }
60     }

从上述代码来看,对构造函数中属性的解析,经历了如下过程(大致结果,上述代码注释已基本可以清楚了解):

(1)略过description或者meta。

(2)提取constructor-arg上的ref和value属性,以便于根据规则验证正确性。

(3)ref属性的处理。在Spring配置中使用<constructor-arg ref="aaaaa">。

(4)value属性的处理。在Spring配置中使用<constructor-arg value="aaaaa">。

(5)子元素的处理。在Spring配置中使用:

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

子元素property

先来回顾一下property的使用方式:

<bean id="test" class="com.joe.Hello">
    <property name="testStr" value="Hello"/>
</bean>
或者
<bean id="test">
    <property name="pro">
        <list>
            <value>aaaa</value>
            <value>bbbbb</value>
        </list>
    </property>
</bean>

Spring源码的解析过程是:

public void parsePropertyElements(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, PROPERTY_ELEMENT)) {
                parsePropertyElement((Element) node, bd);
            }
        }
    }

这个函数先提取所有的property的子元素,然后调用parsePropertyElement处理,来看这个方法的源码:

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 {
            //不允许对同一属性进行多次配置
            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();
        }
    }

可以看出与构造函数注入方式不同的是将返回值使用PropertyValue进行封装,并记录在了BeanDefinition中的propertyValues属性中。

子元素qualifier

对于qualifier元素的获取,接触得更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选bean数目必须有且仅有一个。当找不到一个匹配的bean时,Spring容器将抛出BeanCreationException异常,并指出必须至少拥有一个匹配的bean。

Spring允许我们通过qualifier指定注入的bean名称,这样歧义就消除了,使用方式如下:

<bean id="hello" class="com.joe.Hello">
    <qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/>
</bean>

Spring解析的源码:

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);
            }
        }
    }
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
        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 qualifier = new AutowireCandidateQualifier(typeName);
            qualifier.setSource(extractSource(ele));
            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);
                if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
                    Element attributeEle = (Element) node;
                    String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
                    String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
                    if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
                        BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
                        attribute.setSource(extractSource(attributeEle));
                        qualifier.addMetadataAttribute(attribute);
                    }
                    else {
                        error("Qualifier ‘attribute‘ tag must have a ‘name‘ and ‘value‘", attributeEle);
                        return;
                    }
                }
            }
            bd.addQualifier(qualifier);
        }
        finally {
            this.parseState.pop();
        }
    }

可以看出其解析过程与property的解析大同小异。就不再赘述。

参考:《Spring源码深度解析》 郝佳 编著:

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

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

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

spring bean属性及子元素使用总结

jquery中的$的特殊用法

C# 将 XML 解析为带有子元素的对象

spring-beans中Aware相关接口的源码解析说明