基于 Spring Framework v5.2.6.RELEASE
前情提要
之前的两源码阅读中,分析了 Spring 加载 BeanDefinition 过程中准备阶段和 XML 加载阶段的流程,可以参考:
上一篇提到了XmlBeanDefinitionReader
类的doLoadBeanDefinitions
方法中有两行关键的代码:
Documentdoc=doLoadDocument(inputSource, resource); intcount=registerBeanDefinitions(doc, resource);
第一行是将 XML 配置文件资源加载并解析成 Document 对象,这是 XML 文件解析的过程,是上一篇的主要内容。第二行是注册 BeanDefinition 的过程。
BeanDefinition 的注册其实可以分为两个部分,分别是把 Document 对象中的内容解析成 BeanDefinition 的过程,和把 BeanDefinition 注册到容器中的过程。
这篇接着之前的内容,分析 XML 资源加载成 Document 对象之后,是如何被解析成 BeanDefinition 的。
BeanDefinition 的解析
下面从int count = registerBeanDefinitions(doc, resource)
这行代码入手,先找到registerBeanDefinitions
方法的源码:
publicintregisterBeanDefinitions(Documentdoc, Resourceresource) throwsBeanDefinitionStoreException { BeanDefinitionDocumentReaderdocumentReader=createBeanDefinitionDocumentReader(); intcountBefore=getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); returngetRegistry().getBeanDefinitionCount() -countBefore; }
下面罗列一下方法中的逻辑:
- 创建一个 BeanDefinitionDocumentReader 对象
documentReader
- 获取 Spring 容器中已经注册的 BeanDefinition 的数量
调用documentReader
的registerBeanDefinitions
方法,初步分析这里完成了解析和向容器中注册 BeanDefinition 的过程。- 通过简单运算得到本次注册的 BeanDefinition 的数量并返回。
这里主要关注一下步骤1和3。
documentReader
的创建
首先简单看一下documentReader
是如何创建的:
protectedBeanDefinitionDocumentReadercreateBeanDefinitionDocumentReader() { returnBeanUtils.instantiateClass(this.documentReaderClass); }
privateClass<?extendsBeanDefinitionDocumentReader>documentReaderClass=DefaultBeanDefinitionDocumentReader.class;
这里从createBeanDefinitionDocumentReader
方法和documentReaderClass
成员变量的代码可以看出,这里通过反射创建了一个 DefaultBeanDefinitionDocumentReader 类型的对象。
下面再看最重要的registerBeanDefinitions
方法:
publicvoidregisterBeanDefinitions(Documentdoc, XmlReaderContextreaderContext) { this.readerContext=readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
BeanDefinitionParserDelegate
的创建
这里看到了一个名为doXXX
的方法调用,应该就是这部分的核心逻辑了,这里传入的参数是doc.getDocumentElement()
,代表的是 XML 文件中的beans
标签。查看方法源码:
/*** Register each bean definition within the given root {@code <beans/>} element.*/"deprecation") // for Environment.acceptsProfiles(String...) (protectedvoiddoRegisterBeanDefinitions(Elementroot) { // Any nested <beans> elements will cause recursion in this method. In// order to propagate and preserve <beans> default-* attributes correctly,// keep track of the current (parent) delegate, which may be null. Create// the new (child) delegate with a reference to the parent for fallback purposes,// then ultimately reset this.delegate back to its original (parent) reference.// this behavior emulates a stack of delegates without actually necessitating one.BeanDefinitionParserDelegateparent=this.delegate; this.delegate=createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { StringprofileSpec=root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles=StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported// in XML config. See SPR-12458 for details.if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles ["+profileSpec+"] not matching: "+getReaderContext().getResource()); } return; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate=parent; }
这里先解释一下方法体中第一行和最后一行代码,这两行代码看起来是在开头将
this.delegate
成员变量赋值给了一个局部变量,在方法末尾在还原,在中间部分,会创建新的delegate
来执行真正的工作。这么做的原因是,如果在配置文件中,一个beans
标签中又嵌套了另一个beans
标签,那么会递归调用这个方法,这两句代码其实是在处理每一层递归栈各自的状态。不过这里我还是存有个疑问
- 根据实验,Spring 应该不允许 beans 标签嵌套 beans 标签,这里为什么要这么处理?
希望关于这个问题有想法的小伙伴可以一起交流。
在上面的方法源码中,先看delegate
的创建:
protectedBeanDefinitionParserDelegatecreateDelegate( XmlReaderContextreaderContext, Elementroot, BeanDefinitionParserDelegateparentDelegate) { BeanDefinitionParserDelegatedelegate=newBeanDefinitionParserDelegate(readerContext); delegate.initDefaults(root, parentDelegate); returndelegate; }
这里先知道delegate
是 BeanDefinitionParserDelegate 类型的,从名字可以初步推断出,它是一个从 Document 中解析 BeanDefinition 的代理类,后面具体解析的时候,我们再做分析。
这个方法中最核心的部分是后面的三行代码:
preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root);
其中,preProcessXml
和postProcessXml
两个方法的方法体是空的,且方法是protected
修饰的,因此可以推断,这两个方法是留给子类的扩展点。
匹配标签解析的逻辑
下面重点看parseBeanDefinitions
方法:
protectedvoidparseBeanDefinitions(Elementroot, BeanDefinitionParserDelegatedelegate) { if (delegate.isDefaultNamespace(root)) { NodeListnl=root.getChildNodes(); for (inti=0; i<nl.getLength(); i++) { Nodenode=nl.item(i); if (nodeinstanceofElement) { Elementele= (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
从这里的代码可以大概看出,要真正开始对 Document 的内容进行解析了。
这里先简单介绍一下 Node 和 Element。Element 是 XML 中的一个标签,而 Node 是 XML 中的一个节点,节点可以是标签,也可以是标签的属性或者标签体中的文本内容。
接下来看第一行if
判断语句delegate.isDefaultNamespace(root)
,判断一个元素是不是默认命名空间,先找到这个方法的具体内容:
publicbooleanisDefaultNamespace(Nodenode) { returnisDefaultNamespace(getNamespaceURI(node)); }
publicbooleanisDefaultNamespace(StringnamespaceUri) { return (!StringUtils.hasLength(namespaceUri) ||BEANS_NAMESPACE_URI.equals(namespaceUri)); }
publicstaticfinalStringBEANS_NAMESPACE_URI="http://www.springframework.org/schema/beans";
根据以上的三段代码分析,这里的逻辑是,获取到元素的namespaceUri
如果它是空的,或者是http://www.springframework.org/schema/beans
,那么它就是默认命名空间,也就是默认标签。
回到上面的parseBeanDefinitions
方法,我们可以总结这段逻辑:
- 判断当前处理的是不是默认标签,如果不是,就执行自定义标签的解析逻辑
- 遍历当前标签的所有子节点
- 如果子节点也是标签的话,判断它是不是默认标签,如果是就执行默认标签的解析逻辑,如果不是则执行自定义标签的解析逻辑
这里的默认标签指的是beans
、bean
等标签,除了默认标签以外,还有很多你一定见过的自定义标签,比如<context:component-scan/>
等。之后的分析只考虑分析默认标签的情况。
在进入到默认标签处理逻辑parseDefaultElement
方法中:
privatevoidparseDefaultElement(Elementele, BeanDefinitionParserDelegatedelegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } elseif (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } elseif (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } elseif (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recursedoRegisterBeanDefinitions(ele); } }
来到这里以后,会根据标签的名称来执行不同的解析逻辑。
如果是beans标签,那么又会执行前面介绍过的doRegisterBeanDefinitions
方法,这也是前面提到这个方法可能会递归调用的原因。不过根据前面的代码分析,我们这里要解析的是bean标签的内容,因此会进入到processBeanDefinition(ele, delegate);
这个方法调用中。
bean
标签元素信息的解析
我们继续深入代码:
protectedvoidprocessBeanDefinition(Elementele, BeanDefinitionParserDelegatedelegate) { BeanDefinitionHolderbdHolder=delegate.parseBeanDefinitionElement(ele); if (bdHolder!=null) { bdHolder=delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance.BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreExceptionex) { getReaderContext().error("Failed to register bean definition with name '"+bdHolder.getBeanName() +"'", ele, ex); } // Send registration event.getReaderContext().fireComponentRegistered(newBeanComponentDefinition(bdHolder)); } }
这里可以看到,最终解析的工作还是delegate
来完成的,解析的结果是一个 BeanDefinitionHolder 的对象bdHolder
,根据名称推断,这个对象会持有解析出的 BeanDefinition。之后,在try
语句块中,将解析的结果注册到容器中。
注册的具体过程我们放到下一篇,接下来看解析的过程。进入delegate
的parseBeanDefinitionElement
方法:
publicBeanDefinitionHolderparseBeanDefinitionElement(Elementele) { returnparseBeanDefinitionElement(ele, null); }
调用重载方法:
publicBeanDefinitionHolderparseBeanDefinitionElement(Elementele, BeanDefinitioncontainingBean) { // 第1部分Stringid=ele.getAttribute(ID_ATTRIBUTE); StringnameAttr=ele.getAttribute(NAME_ATTRIBUTE); // 第2部分List<String>aliases=newArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr=StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } // 第3部分StringbeanName=id; if (!StringUtils.hasText(beanName) &&!aliases.isEmpty()) { beanName=aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '"+beanName+"' as bean name and "+aliases+" as aliases"); } } if (containingBean==null) { checkNameUniqueness(beanName, aliases, ele); } // 第4部分AbstractBeanDefinitionbeanDefinition=parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition!=null) { if (!StringUtils.hasText(beanName)) { /* 省略,因为我们目前分析的流程中,beanName 不可能为空 */ } String[] aliasesArray=StringUtils.toStringArray(aliases); returnnewBeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } returnnull; }
这里我通过注释将代码分成了4部分,下面分别来介绍这4部分的逻辑:
- 从 XML 标签中获取其中的
id
和name
属性。 - 处理别名,因为 Spring 允许给 Bean 配置多个名称,这一步就是解析出这些名称,放到
aliases
数组中。 - 将
id
作为 Bean 在容器中的唯一名称,如果没有给 Bean 配置id
,则从aliases
中取出第一个作为 Bean 的名称,并检查名称的唯一性。 - 解析 Bean 对应的标签,并得到 AbstractBeanDefinition 类型的结果,如果结果不为空,则封装 BeanDefinitionHolder 对象并返回。
bean
标签元素内容的解析
接下来,在进入到parseBeanDefinitionElement
方法中,查看 Bean 对应的标签解析的过程:
publicAbstractBeanDefinitionparseBeanDefinitionElement( Elementele, StringbeanName, BeanDefinitioncontainingBean) { this.parseState.push(newBeanEntry(beanName)); StringclassName=null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className=ele.getAttribute(CLASS_ATTRIBUTE).trim(); } Stringparent=null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent=ele.getAttribute(PARENT_ATTRIBUTE); } try { AbstractBeanDefinitionbd=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)); returnbd; } /* 省略异常处理逻辑 */finally { this.parseState.pop(); } returnnull; }
这里的主要流程在try语句块中,我分别来介绍一下这些代码都干了什么:
- 第一行代码中,通过
createBeanDefinition
方法创建了一个AbstractBeanDefinition
。 - 接下来的两行代码,解析了
bean
标签中的各种属性,并封装到了bd
中。(其中的代码比较长且逻辑简单,感兴趣的话可以自行查看) - 接下来的6行代码,分别解析了bean的各种子标签的信息,并封装到
bd
中,这些子标签包括meta
、lookup-method
、replaced-method
、constructor-arg
、property
、qualifier
,关于这些子标签的信息不在本文讨论范围,不过你应该对它们都很熟悉。 - 最后添加了一些附加信息之后将
bd
返回。
BeanDefinition 的创建
这里我们再看一下createBeanDefinition
方法是如何创建 BeanDefinition 的:
protectedAbstractBeanDefinitioncreateBeanDefinition(StringclassName, StringparentName) throwsClassNotFoundException { returnBeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); }
这里在继续查看BeanDefinitionReaderUtils.createBeanDefinition
的源码:
publicstaticAbstractBeanDefinitioncreateBeanDefinition( StringparentName, StringclassName, ClassLoaderclassLoader) throwsClassNotFoundException { GenericBeanDefinitionbd=newGenericBeanDefinition(); bd.setParentName(parentName); if (className!=null) { if (classLoader!=null) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } returnbd; }
这里可以看到,我们实际创建的 BeanDefinition 的类型是 GenericBeanDefinition。
后续
至此,将 Document 解析成 BeanDefinition 的过程,就分析到这里,对于bean标签的子标签的解析过程,将不再分析,因为其中的内容比较多,且逻辑简单,其核心任务都是将标签中解析到的信息封装到 BeanDefinition 当中。至此我们已经了解了整个过程的逻辑,想了解更细节的解析过程,可以自己继续深入阅读代码。
下一篇讲分析 Spring 是如何将 BeanDefinition 注册到容器中的。