乐趣区

关于spring:Spring-源码第-8-篇各种属性的解析

Spring 源码解析第 8 篇,持续。

上篇文章咱们剖析了 bean 标签的解析过程,然而次要是波及到一些简略的属性,一些冷门属性如 lookup-method 等没有和大家剖析,次要是思考到这些属性大家可能用得少,因而我上周录制了一个简略的视频,先率领小伙伴们温习了一下这些冷门属性的用法:

Spring 中四个冷门属性,你可能没用过,挑战看一下!

当初对于 bean 节点的配置大家都理解了,咱们接下来就来看下残缺的解析过程。

浏览本系列后面文章,有助于更好的了解本文:

  1. Spring 源码解读打算
  2. Spring 源码第一篇开整!配置文件是怎么加载的?
  3. Spring 源码第二弹!XML 文件解析流程
  4. Spring 源码第三弹!EntityResolver 是个什么鬼?
  5. Spring 源码第四弹!深刻了解 BeanDefinition
  6. 手把手教你搭建 Spring 源码剖析环境
  7. Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory
  8. Spring 源码解读第七弹!bean 标签的解析

1. 解析办法回顾

上篇文章咱们最终剖析到上面这个办法:

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
    try {AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        parseConstructorArgElements(ele, bd);
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);
        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));
        return bd;
    }
    catch (ClassNotFoundException ex) {error("Bean class [" + className + "] not found", ele, ex);
    }
    catch (NoClassDefFoundError err) {error("Class that bean class [" + className + "] depends on not found", ele, err);
    }
    catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);
    }
    finally {this.parseState.pop();
    }
    return null;
}

parseBeanDefinitionAttributes 办法用来解析一般属性,咱们曾经在上篇文章中剖析过了,这里不再赘述,明天次要来看看其余几个办法的解析工作。

2.Description

首先是 description 的解析,间接通过 DomUtils.getChildElementValueByTagName 工具办法从节点中取出 description 属性的值。这个没啥好说的。

小伙伴们在剖析源码时,这些工具办法如果你不确定它的性能,或者想验证它的其余用法,能够通过 IDEA 提供的 Evaluate Expression 性能现场调用该办法,进而验证本人想法,就是下图标进去的那个计算器小图标,点击之后,输出你想执行的代码:

3.parseMetaElements

这个办法次要是解析 meta 属性。后面的视频中曾经讲了,这个 meta 属性是保留在 BeanDefinition 中的,也是从 BeanDefinition 中获取的,依照这个思路,来看解析代码就很容易懂了:

public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {NodeList nl = ele.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {Element metaElement = (Element) node;
            String key = metaElement.getAttribute(KEY_ATTRIBUTE);
            String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
            BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
            attribute.setSource(extractSource(metaElement));
            attributeAccessor.addMetadataAttribute(attribute);
        }
    }
}

能够看到,遍历元素,从中提取出 meta 元素的值,并构建出 BeanMetadataAttribute 对象,最初存入 GenericBeanDefinition 对象中。

有小伙伴说不是存入 BeanMetadataAttributeAccessor 中吗?这其实是 GenericBeanDefinition 的父类,BeanMetadataAttributeAccessor 专门用来解决属性的加载和读取,相干介绍能够参考松哥后面的文章:

  • Spring 源码第四弹!深刻了解 BeanDefinition

4.parseLookupOverrideSubElements

这个办法是为了解析出 lookup-method 属性,在后面的视频中松哥曾经和大家聊过,lookup-method 能够动静替换运行的办法,依照这个思路,咱们来看下这个办法的源码:

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {Element ele = (Element) node;
            String methodName = ele.getAttribute(NAME_ATTRIBUTE);
            String beanRef = ele.getAttribute(BEAN_ELEMENT);
            LookupOverride override = new LookupOverride(methodName, beanRef);
            override.setSource(extractSource(ele));
            overrides.addOverride(override);
        }
    }
}

能够看到,在这里遍历元素,从 lookup-method 属性中,取出来 methodName 和 beanRef 属性,结构出 LookupOverride 而后存入 GenericBeanDefinition 的 methodOverrides 属性中。

存入 GenericBeanDefinition 的 methodOverrides 属性中之后,咱们也能够在代码中查看:

5.parseReplacedMethodSubElements

parseReplacedMethodSubElements 这个办法次要是解析 replace-method 属性的,依据后面视频的解说,replace-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);
        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);
        }
    }
}

name 获取到的是要替换的旧办法,callback 则是获取到的要替换的新办法,接下来再去结构 ReplaceOverride 对象。

另外因为 replace-method 外部还能够再配置参数类型,所以在结构完 ReplaceOverride 对象之后,接下来还要去解析 arg-type。

6.parseConstructorArgElements

parseConstructorArgElements 这个办法次要是用来解析构造方法的。这个大家日常开发中应该接触的很多。如果小伙伴们对于各种各样的构造方法注入还不太熟悉,能够在微信公众号江南一点雨后盾回复 spring5,获取松哥之前录制的收费 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);
        }
    }
}
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
    String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
    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));
                    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 {
        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();
        }
    }
}

能够看到,构造函数最终在 parseConstructorArgElement 办法中解析。

  1. 一开始先去获取 name,index 以及 value 属性,因为构造方法的参数能够指定 name,也能够指定下标。
  2. 先去判断 index 是否有值,进而决定依照 index 解析还是依照 name 解析。
  3. 无论哪种解析形式,都是通过 parsePropertyValue 办法将 value 解析进去。
  4. 解析进去的子元素保留在 ConstructorArgumentValues.ValueHolder 对象中。
  5. 如果是通过 index 来解析参数,最终调用 addIndexedArgumentValue 办法保留解析后果,如果是通过 name 来解析参数,最终通过 addGenericArgumentValue 办法来保留解析后果。

7.parsePropertyElements

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);
        }
    }
}
public void parsePropertyElement(Element ele, BeanDefinition bd) {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();
    }
}

后面看了那么多,再看这个办法就比较简单了。这里最终还是通过 parsePropertyValue 办法解析出 value,并调用 addPropertyValue 办法来存入相干的值。

8.parseQualifierElements

parseQualifierElements 就是用来解析 qualifier 节点的,最终也是保留在对应的属性中。解析过程和后面的相似,我就不再赘述了,咱们来看下解析后果:

9. 再谈 BeanDefinition

这里的属性解析完了都是保留在 GenericBeanDefinition 对象中,而该对象未来能够用来构建一个 Bean。

在 Spring 容器中,咱们宽泛应用的是一个一个的 Bean,BeanDefinition 从名字上就能够看出是对于 Bean 的定义。

事实上就是这样,咱们在 XML 文件中配置的 Bean 的各种属性,这些属性不仅仅是和对象相干,Spring 容器还要解决 Bean 的生命周期、销毁、初始化等等各种操作,咱们定义的对于 Bean 的生命周期、销毁、初始化等操作总得有一个对象来承载,那么这个对象就是 BeanDefinition。

XML 中定义的各种属性都会先加载到 BeanDefinition 上,而后通过 BeanDefinition 来生成一个 Bean,从这个角度来说,BeanDefinition 和 Bean 的关系有点相似于类和对象的关系。

在 Spring 中,次要有三种类型的 BeanDefinition:

  • RootBeanDefinition
  • ChildBeanDefinition
  • GenericBeanDefinition

在 Spring 中,如果咱们为一个 Bean 配置了父 Bean,父 Bean 将被解析为 RootBeanDefinition,子 Bean 被解析为 ChildBeanDefinition,要是没有父 Bean,则被解析为 RootBeanDefinition。

GenericBeanDefinition 是从 Spring2.5 当前新退出的 BeanDefinition 实现类。GenericBeanDefinition 能够动静设置父 Bean,同时兼具 RootBeanDefinition 和 ChildBeanDefinition 的性能。

目前广泛应用的就是 GenericBeanDefinition,所以咱们看到后面的解析后果也是保留到 GenericBeanDefinition 中的。

对于 BeanDefinition 的更多信息,小伙伴们能够参考松哥之前的文章:Spring 源码第四弹!深刻了解 BeanDefinition

好啦,Spring 第 8 篇,咱们就先聊这么多,小伙伴们要是感觉有播种,记得点个在看激励下哦~

退出移动版