2022-08-23  阅读(49)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/series/article/1711901392

上篇博客(【死磕 Spring】—– IOC 之解析 bean 标签:meta、lookup-method、replace-method)分析了 meta 、 lookup-method、replace-method 三个子元素,这篇博客分析 constructor-arg 、property、qualifier 三个子元素。

constructor-arg 子元素

举个小栗子:

            public class StudentService {
                private String name;
            
                private Integer age;
            
                private BookService bookService;
            
                StudentService(String name, Integer age, BookService bookService){
                    this.name = name;
                    this.age = age;
                    this.bookService = bookService;
                }
            }
            
                <bean id="bookService" class="org.springframework.core.service.BookService"/>
            
                <bean id="studentService" class="org.springframework.core.service.StudentService">
                    <constructor-arg index="0" value="chenssy"/>
                    <constructor-arg name="age" value="100"/>
                    <constructor-arg name="bookService" ref="bookService"/>
                </bean>

StudentService 定义一个构造函数,配置文件中使用 constructor-arg 元素对其配置,该元素可以实现对 StudentService 自动寻找对应的构造函数,并在初始化的时候将值当做参数进行设置。parseConstructorArgElements() 方法完成 constructor-arg 子元素的解析。

                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、type、name 属性值
                    String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
                    String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
                    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
            
                    // 如果有index
                    if (StringUtils.hasLength(indexAttr)) {
                        try {
                            int index = Integer.parseInt(indexAttr);
                            if (index < 0) {
                                error("'index' cannot be lower than 0", ele);
                            }
                            else {
                                try {
                                    // 构造一个 ConstructorArgumentEntry 并将其加入到 ParseState 中
                                    this.parseState.push(new ConstructorArgumentEntry(index));
            
                                    // 解析 ele 对应属性元素
                                    Object value = parsePropertyValue(ele, bd, null);
            
                                    // 根据解析的属性元素构造一个 valueHolder 对象
                                    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 {
                                        // 加入到 indexedArgumentValues 中国
                                        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 {
                            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();
                        }
                    }
                }

首先获取 index、type、name 三个属性值,然后根据是否存在 index 来区分。其实两者逻辑都差不多,总共分为如下几个步骤(以有 index 为例):

  1. 构造 ConstructorArgumentEntry 对象并将其加入到 ParseState 队列中。ConstructorArgumentEntry 表示构造函数的参数。
  2. 调用 parsePropertyValue() 解析 constructor-arg 子元素,返回结果值
  3. 根据解析的结果值构造 ConstructorArgumentValues.ValueHolder 实例对象
  4. 将 type、name 封装到 ConstructorArgumentValues.ValueHolder 中,然后将 ValueHolder 实例对象添加到 indexedArgumentValues 中。

无 index 的处理逻辑差不多,只有几点不同:构造 ConstructorArgumentEntry 对象时是调用无参构造函数;最后是将 ValueHolder 实例添加到 genericArgumentValues 中。 parsePropertyValue() 对子元素进一步解析。

                public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
                    String elementName = (propertyName != null) ?
                            "<property> element for property '" + propertyName + "'" :
                            "<constructor-arg> element";
            
                    NodeList nl = ele.getChildNodes();
                    Element subElement = null;
                    for (int i = 0; i < nl.getLength(); i++) {
                        Node node = nl.item(i);
                        // meta 、description 不处理
                        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;
                            }
                        }
                    }
            
                    // 解析 ref 元素
                    boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
                    // 解析 value 元素
                    boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
            
                    // constructor-arg 子元素有两种情况不存在
                    // 1. 即存在 ref 又存在 value
                    // 2. 存在 ref 或者 value,但是又有子元素
                    if ((hasRefAttribute && hasValueAttribute) ||
                            ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
                        error(elementName +
                                " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
                    }
            
                    if (hasRefAttribute) {
                        // 获取 ref 属性值
                        String refName = ele.getAttribute(REF_ATTRIBUTE);
                        if (!StringUtils.hasText(refName)) {
                            error(elementName + " contains empty 'ref' attribute", ele);
                        }
                        // 将 ref 属性值构造为 RuntimeBeanReference 实例对象
                        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 {
                        // Neither child element nor "ref" or "value" attribute found.
                        error(elementName + " must specify a ref or value", ele);
                        return null;
                    }
                }
  1. 提取 constructor-arg 子元素的 ref 和 value 的属性值,对其进行判断,以下两种情况是不允许存在的

    • ref 和 value 属性同时存在
    • 存在 ref 或者 value 且又有子元素
  2. 若存在 ref 属性,则获取其值并将其封装进 RuntimeBeanReference 实例对象中

  3. 若存在 value 属性,则获取其值并将其封装进 TypedStringValue 实例对象中

  4. 如果子元素不为空,则调用 parsePropertySubElement() 进行子元素进一步处理

对于 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;
                    }
                    else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
                        return parseIdRefElement(ele);
                    }
                    else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
                        return parseValueElement(ele, defaultValueType);
                    }
                    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)) {
                        return parseArrayElement(ele, bd);
                    }
                    else if (nodeNameEquals(ele, LIST_ELEMENT)) {
                        return parseListElement(ele, bd);
                    }
                    else if (nodeNameEquals(ele, SET_ELEMENT)) {
                        return parseSetElement(ele, bd);
                    }
                    else if (nodeNameEquals(ele, MAP_ELEMENT)) {
                        return parseMapElement(ele, bd);
                    }
                    else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
                        return parsePropsElement(ele);
                    }
                    else {
                        error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
                        return null;
                    }
                }

上面对各个子类进行分类处理,详细情况如果各位有兴趣可以移步源码进行深一步的探究。

property 子元素

我们一般使用如下方式来使用 property 子元素。

                <bean id="studentService" class="org.springframework.core.service.StudentService">
                    <property name="name" value="chenssy"/>
                    <property name="age" value="18"/>
                </bean>

对于 property 子元素的解析,Spring 调用 parsePropertyElements()。如下:

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

和 constructor-arg 子元素差不多,同样是提取所有的 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 {
                        // 如果存在相同的 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);
                        parseMetaElements(ele, pv);
                        pv.setSource(extractSource(ele));
                        // 添加到 MutablePropertyValues 中
                        bd.getPropertyValues().addPropertyValue(pv);
                    }
                    finally {
                        this.parseState.pop();
                    }
                }

与解析 constructor-arg 子元素步骤差不多。调用 parsePropertyValue() 解析子元素属性值,然后根据该值构造 PropertyValue 实例对象并将其添加到 BeanDefinition 中的 MutablePropertyValues 中。



Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文