• 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源码深度解析》 郝佳 编著:

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    Redis——各个数据类型最大存储量
    Redis——数据结构
    MongoDB——分片片键的选择 (转)
    Java—— ThreadLocal (转)
    Python——关于安装和基本命令记录
    Unity3d——UI框架的设计 (转)
    Java——中介者(Mediator)模式 (转)
    Java——Java基础——100个问题汇总 (转)
    Actor——Actor模型介绍 (转)
    run keyword if
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10099237.html
Copyright © 2020-2023  润新知