Spring 源码第 8 篇,各种属性的解析

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 专门用来处理属性的加载和读取,相关介绍可以参考松哥前面的文章:

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 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 的定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值