共计 9353 个字符,预计需要花费 24 分钟才能阅读完成。
Spring 源码解析第 8 篇,持续。
上篇文章咱们剖析了 bean 标签的解析过程,然而次要是波及到一些简略的属性,一些冷门属性如 lookup-method 等没有和大家剖析,次要是思考到这些属性大家可能用得少,因而我上周录制了一个简略的视频,先率领小伙伴们温习了一下这些冷门属性的用法:
Spring 中四个冷门属性,你可能没用过,挑战看一下!
当初对于 bean 节点的配置大家都理解了,咱们接下来就来看下残缺的解析过程。
浏览本系列后面文章,有助于更好的了解本文:
- Spring 源码解读打算
- Spring 源码第一篇开整!配置文件是怎么加载的?
- Spring 源码第二弹!XML 文件解析流程
- Spring 源码第三弹!EntityResolver 是个什么鬼?
- Spring 源码第四弹!深刻了解 BeanDefinition
- 手把手教你搭建 Spring 源码剖析环境
- Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory
- 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 办法中解析。
- 一开始先去获取 name,index 以及 value 属性,因为构造方法的参数能够指定 name,也能够指定下标。
- 先去判断 index 是否有值,进而决定依照 index 解析还是依照 name 解析。
- 无论哪种解析形式,都是通过 parsePropertyValue 办法将 value 解析进去。
- 解析进去的子元素保留在 ConstructorArgumentValues.ValueHolder 对象中。
- 如果是通过 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 篇,咱们就先聊这么多,小伙伴们要是感觉有播种,记得点个在看激励下哦~