曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

写在前面的话#

相关背景及资源:

曹工说 Spring Boot 源码(1)-- Bean Definition 到底是什么,附 spring 思维导图分享

曹工说 Spring Boot 源码(2)-- Bean Definition 到底是什么,咱们对着接口,逐个方法讲解

曹工说 Spring Boot 源码(3)-- 手动注册 Bean Definition 不比游戏好玩吗,我们来试一下

曹工说 Spring Boot 源码(4)-- 我是怎么自定义 ApplicationContext,从 json 文件读取 bean definition 的?

曹工说 Spring Boot 源码(5)-- 怎么从 properties 文件读取 bean

工程代码地址 思维导图地址

工程结构图:

整体流程#

这次,我们打算讲一下,spring 启动时,是怎么去读取 xml 文件的,bean 的解析部分可能暂时涉及不到,因为放到一起,内容就太多了,具体再看吧。

  1. ClassPathXmlApplicationContext设置 xml 文件的路径

  2. refresh内部的beanFactory,其实这时候BeanFactory都还没创建,会先创DefaultListableBeanFactory

  3. ClassPathXmlApplicationContext调用其loadBeanDefinitions,将新建DefaultListableBeanFactory作为参数传入

  4. ClassPathXmlApplicationContext内部会持有一个XmlBeanDefinitionReader,且XmlBeanDefinitionReader内部是持有之前创建的DefaultListableBeanFactory的,这时候就简单了,XmlBeanDefinitionReader负责读取 xml,将bean definition 解析出来,丢给DefaultListableBeanFactory,此时,XmlBeanDefinitionReader就算完成了,退出历史舞台

  5. 上面第四步完成后,DefaultListableBeanFactory里面其实一个业务 bean 都没有,只有一堆的 bean definition,后面ClassPathXmlApplicationContext直接去实例化那些需要在启动过程中实例化的 bean。

    当前,这中间还涉及到使用beanDefinitionPostProcessorbeanPostProcessor对 beanFactory 进行处理,这都是后话了。

这次,我们主要讲的部分是,第 4 步。

入口代码#

Copy
位置:org.springframework.context.support.AbstractRefreshableApplicationContext
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">refreshBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeansException</span> <span class="token punctuation">{</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">hasBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token function">destroyBeans</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token function">closeBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
	<span class="token keyword">try</span> <span class="token punctuation">{</span>
		<span class="token comment">//创建一个DefaultListableBeanFactory</span>
		<span class="token class-name">DefaultListableBeanFactory</span> beanFactory <span class="token operator">=</span> <span class="token function">createBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		beanFactory<span class="token punctuation">.</span><span class="token function">setSerializationId</span><span class="token punctuation">(</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token function">customizeBeanFactory</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token comment">// 加载</span>
		<span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>beanFactoryMonitor<span class="token punctuation">)</span> <span class="token punctuation">{</span>
			<span class="token keyword">this</span><span class="token punctuation">.</span>beanFactory <span class="token operator">=</span> beanFactory<span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>
	<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ApplicationContextException</span><span class="token punctuation">(</span><span class="token string">"I/O error parsing bean definition source for "</span> <span class="token operator">+</span> <span class="token function">getDisplayName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

上面调用了

Copy
AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
Copy
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { // 创建一个从 xml 读取 beanDefinition 的读取器 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
	<span class="token comment">// 配置环境</span>
	beanDefinitionReader<span class="token punctuation">.</span><span class="token function">setEnvironment</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token comment">// 配置资源loader,一般就是classpathx下去获取xml</span>
	beanDefinitionReader<span class="token punctuation">.</span><span class="token function">setResourceLoader</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token comment">// xml解析的解析器</span>
	beanDefinitionReader<span class="token punctuation">.</span><span class="token function">setEntityResolver</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ResourceEntityResolver</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

	<span class="token comment">// Allow a subclass to provide custom initialization of the reader,</span>
	<span class="token comment">// then proceed with actually loading the bean definitions.</span>
	<span class="token function">initBeanDefinitionReader</span><span class="token punctuation">(</span>beanDefinitionReader<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token comment">// 核心方法,使用beanDefinitionReader去解析xml,并将解析后的bean definition放到beanFactory</span>
	<span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span>beanDefinitionReader<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

xml 中 xsd、dtd 解析器#

Copy
// xml 解析的解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

这个类实现的接口就是 jdk 里面的org.xml.sax.EntityResolver,这个接口,只有一个方法,主要负责 xml 里,外部实体的解析:

Copy
public interface EntityResolver {
<span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token class-name">InputSource</span> resolveEntity <span class="token punctuation">(</span><span class="token class-name">String</span> publicId<span class="token punctuation">,</span>
                                           <span class="token class-name">String</span> systemId<span class="token punctuation">)</span>
    <span class="token keyword">throws</span> <span class="token class-name">SAXException</span><span class="token punctuation">,</span> <span class="token class-name">IOException</span><span class="token punctuation">;</span>

}

大家可能不太明白,我们看看怎么实现的吧:

Copy
public class DelegatingEntityResolver implements EntityResolver {
<span class="token comment">/** Suffix for DTD files */</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DTD_SUFFIX</span> <span class="token operator">=</span> <span class="token string">".dtd"</span><span class="token punctuation">;</span>

<span class="token comment">/** Suffix for schema definition files */</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">XSD_SUFFIX</span> <span class="token operator">=</span> <span class="token string">".xsd"</span><span class="token punctuation">;</span>


<span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EntityResolver</span> dtdResolver<span class="token punctuation">;</span>

<span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EntityResolver</span> schemaResolver<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token class-name">DelegatingEntityResolver</span><span class="token punctuation">(</span><span class="token class-name">ClassLoader</span> classLoader<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">this</span><span class="token punctuation">.</span>dtdResolver <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BeansDtdResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">this</span><span class="token punctuation">.</span>schemaResolver <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PluggableSchemaResolver</span><span class="token punctuation">(</span>classLoader<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 主要看这里,感觉就是对我们xml里面的那堆xsd进行解析</span>
<span class="token annotation punctuation">@override</span>  
<span class="token keyword">public</span> <span class="token class-name">InputSource</span> <span class="token function">resolveEntity</span><span class="token punctuation">(</span><span class="token class-name">String</span> publicId<span class="token punctuation">,</span> <span class="token class-name">String</span> systemId<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">SAXException</span><span class="token punctuation">,</span> <span class="token class-name">IOException</span> <span class="token punctuation">{</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>systemId <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>systemId<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token constant">DTD_SUFFIX</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>dtdResolver<span class="token punctuation">.</span><span class="token function">resolveEntity</span><span class="token punctuation">(</span>publicId<span class="token punctuation">,</span> systemId<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>systemId<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token constant">XSD_SUFFIX</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>schemaResolver<span class="token punctuation">.</span><span class="token function">resolveEntity</span><span class="token punctuation">(</span>publicId<span class="token punctuation">,</span> systemId<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>
	<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

举个例子,我们 xml 里一般不是有如下代码吗:

Copy
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

上面的代码,应该就是去获取和解析上面这里的xsd,方便进行语法校验的。毕竟,这个 xml 我们也不能随便乱写吧,比如,根元素就是,有且 只能有一个,下面才能有 0 到多个之类的元素。你要是写了多个根元素,肯定不合规范啊。

接下来,我们还是赶紧切入正题吧,看看XmlBeanDefinitionReader是怎么解析 xml 的。

XmlBeanDefinitionReader 解析 xml#

Copy
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); // 这个方法还在:AbstractXmlApplicationContext,获取资源位置,传给 XmlBeanDefinitionReader if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }

经过几个简单跳转,进入下面的方法:

Copy
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();

	<span class="token keyword">if</span> <span class="token punctuation">(</span>resourceLoader <span class="token keyword">instanceof</span> <span class="token class-name">ResourcePatternResolver</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">try</span> <span class="token punctuation">{</span>
			<span class="token class-name">Resource</span><span class="token punctuation">[</span><span class="token punctuation">]</span> resources <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">ResourcePatternResolver</span><span class="token punctuation">)</span> resourceLoader<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span>location<span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token comment">// 从资源数组里load bean definition</span>
			<span class="token keyword">int</span> loadCount <span class="token operator">=</span> <span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span>resources<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">return</span> loadCount<span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>
	
<span class="token punctuation">}</span>

XmlBeanDefinitionReader 类图#

这里插一句,看看其类图:

总的来说,类似于模板设计模式,一些通用的逻辑和流程,放在AbstractBeanDefinitionReader, 具体的解析啊,都是放在子类实现。

我们这里也可以看到,其实现了一个接口,BeanDefinitionReader

Copy
package org.springframework.beans.factory.support;

public interface BeanDefinitionReader {
// 为什么需要这个,因为读取到 bean definition 后,需要存到这个里面去;如果不提供这个,我读了往哪放
BeanDefinitionRegistry getRegistry();
// 资源加载器,加载 xml 之类,当然,作为一个接口,资源是可以来自于任何地方
ResourceLoader getResourceLoader();
// 获取 classloader
ClassLoader getBeanClassLoader();
// beanName 生成器
BeanNameGenerator getBeanNameGenerator();
// 从资源 load bean definition
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

<span class="token keyword">int</span> <span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">Resource</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> resources<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeanDefinitionStoreException</span><span class="token punctuation">;</span>

<span class="token keyword">int</span> <span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">String</span> location<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeanDefinitionStoreException</span><span class="token punctuation">;</span>

<span class="token keyword">int</span> <span class="token function">loadBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> locations<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">BeanDefinitionStoreException</span><span class="token punctuation">;</span>

}

我们切回前面,AbstractBeanDefinitionReader实现了大部分方法,除了下面这个:

Copy
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

因为,它毕竟只是个抽象类,不负责干活啊;而且,为了能够从不同的 resource 读取,这个也理应交给子类。

比如,我们这里的XmlBeanDefinitionReader就是负责从 xml 文件读取;我之前的文章里,也演示了如何从 json 读取,也是自定义了一个AbstractBeanDefinitionReader的子类。

读取 xml 文件为 InputSource#

接着上面的方法往下走,马上就进入到了:

Copy
位置:org.springframework.beans.factory.xml.XmlBeanDefinitionReader,插入的参数,就是我们的xml public int loadBeanDefinitions(EncodedResource encodedResource) { ... try { // 读取 xml 文件为文件流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { // 读取为 xml 资源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 解析 bean definition 去 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } } }

这里,提一句InputSource,这个类的全路径为:org.xml.sax.InputSource,是 jdk 里的类。

包名里包括了 xml,知道大概是 xml 相关的类了,包名也包含了 sax,大概知道是基于 sax 事件解析模型。

这方面我懂得也不多,回头可以单独讲解。我们继续正文:

Copy
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) { try { int validationMode = getValidationModeForResource(resource); // 反正 org.w3c.dom.Document 也是 jdk 的类,具体不管 Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); // 下边就开始正题了 return registerBeanDefinitions(doc, resource); } }

关于documentLoader.loadDocument,大家只要简单知道,是进行初步的解析就行了,主要是基于 xsd、dtd 等,进行语法检查等。

我这里的堆栈,大家看下:

这里,先不深究 xml 解析的东西(主要是我也不怎么会啊,哈哈哈)

接着走:

Copy
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }

BeanDefinitionDocumentReader#

看得出来,XmlBeanDefinitionReader可能觉得工作繁重,于是将具体的工作,交给了BeanDefinitionDocumentReader

Copy
public interface BeanDefinitionDocumentReader {
<span class="token keyword">void</span> <span class="token function">setEnvironment</span><span class="token punctuation">(</span><span class="token class-name">Environment</span> environment<span class="token punctuation">)</span><span class="token punctuation">;</span>


<span class="token keyword">void</span> <span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">Document</span> doc<span class="token punctuation">,</span> <span class="token class-name">XmlReaderContext</span> readerContext<span class="token punctuation">)</span>
		<span class="token keyword">throws</span> <span class="token class-name">BeanDefinitionStoreException</span><span class="token punctuation">;</span>

}

核心方法肯定是

void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)

两个参数,Document我们理解,就是代表 xml 文件;

XmlReaderContext#

XmlReaderContext是啥,看样子是个上下文,上下文,一般来说,就是个大杂烩,把需要用到的都会放在里面。

我们看看放了些啥:

Copy
public class XmlReaderContext extends ReaderContext { // 之前的核心类,把自己传进来了 private final XmlBeanDefinitionReader reader; // org.springframework.beans.factory.xml.NamespaceHandlerResolver,这个也是核心类! private final NamespaceHandlerResolver namespaceHandlerResolver; }

public class ReaderContext {
//xml 资源本身
private final Resource resource;
// 盲猜是中间处理报错后,报告问题
private final ProblemReporter problemReporter;
// event/listener 机制吧,方便扩展
private final ReaderEventListener eventListener;`
// 跳过
private final SourceExtractor sourceExtractor;
}

看完了这个上下文的定义,要知道,它是作为一个参数,传给:

Copy
org.springframework.beans.factory.xml.BeanDefinitionDocumentReader void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException;

那,这个参数怎么构造的呢?

那,我们还得切回前面的代码:

Copy
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); // 这里,调用 createReaderContext(resource) 创建上下文 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
Copy
/** * Create the {@link XmlReaderContext} to pass over to the document reader. */ protected XmlReaderContext createReaderContext(Resource resource) { if (this.namespaceHandlerResolver == null) { // 创建一个 namespacehandler this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.namespaceHandlerResolver); }

namespaceHandlerResolver#

这里太核心了,我们看看namespaceHandlerResolver是干嘛的:

Copy
public interface NamespaceHandlerResolver {
<span class="token comment">/**
 * Resolve the namespace URI and return the located {@link NamespaceHandler}
 * implementation.
 * @param namespaceUri the relevant namespace URI
 * @return the located {@link NamespaceHandler} (may be {@code null})
 */</span>
<span class="token comment">// 比如解析xml时,我们可能有&lt;bean&gt;,这个是默认命名空间,其namespace就是&lt;beans&gt;;如果是&lt;context:component-scan&gt;,这里的namespace,就是context</span>
<span class="token class-name">NamespaceHandler</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token class-name">String</span> namespaceUri<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

这个类的用途,就是根据传入的 namespace,找到对应的 handler。

大家可以去 spring-beans.jar 包里的META-INF/spring.handlers找一找,这个文件打开后,内容如下:

Copy
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

也可以再去 spring-context.jar 里找找,里面也有这个文件:

Copy
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

我列了个表格:

namespace handler
http://www.springframework.org/schema/c org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http://www.springframework.org/schema/p org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http://www.springframework.org/schema/util org.springframework.beans.factory.xml.UtilNamespaceHandler
http://www.springframework.org/schema/context org.springframework.context.config.ContextNamespaceHandler
http://www.springframework.org/schema/task org.springframework.scheduling.config.TaskNamespaceHandler
http://www.springframework.org/schema/cache org.springframework.cache.config.CacheNamespaceHandler

比较重要的,我都列在上面了,剩下的jee/lang,我没用过,不知道大家用过没,先跳过吧。

大家可能也有感到奇怪的地方,怎么没有这个 namespace 的呢,因为它是默认的,所以在程序里是特殊处理的,一会才到它。

接着看核心逻辑:

Copy
DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper(this.readerContext, root, parent);
	<span class="token function">preProcessXml</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">parseBeanDefinitions</span><span class="token punctuation">(</span>root<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>delegate<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">postProcessXml</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>

	<span class="token keyword">this</span><span class="token punctuation">.</span>delegate <span class="token operator">=</span> parent<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

BeanDefinitionParserDelegate#

恩。。。我们已经快疯了,怎么又出来一个类,DefaultBeanDefinitionDocumentReader看来也是觉得自己工作压力太大了吧,这里又弄了个BeanDefinitionParserDelegate

这个类,没实现什么接口,就是个大杂烩,方法多的一批,主要是进行具体的解析工作,大家可以看看里面定义的字段:

Copy
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">MULTI_VALUE_ATTRIBUTE_DELIMITERS</span> <span class="token operator">=</span> <span class="token string">",; "</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Value of a T/F attribute that represents true.
 * Anything else represents false. Case seNsItive.
 */</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">TRUE_VALUE</span> <span class="token operator">=</span> <span class="token string">"true"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">FALSE_VALUE</span> <span class="token operator">=</span> <span class="token string">"false"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEFAULT_VALUE</span> <span class="token operator">=</span> <span class="token string">"default"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DESCRIPTION_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"description"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_NO_VALUE</span> <span class="token operator">=</span> <span class="token string">"no"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_BY_NAME_VALUE</span> <span class="token operator">=</span> <span class="token string">"byName"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_BY_TYPE_VALUE</span> <span class="token operator">=</span> <span class="token string">"byType"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_CONSTRUCTOR_VALUE</span> <span class="token operator">=</span> <span class="token string">"constructor"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_AUTODETECT_VALUE</span> <span class="token operator">=</span> <span class="token string">"autodetect"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE</span> <span class="token operator">=</span> <span class="token string">"all"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE</span> <span class="token operator">=</span> <span class="token string">"simple"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE</span> <span class="token operator">=</span> <span class="token string">"objects"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">NAME_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"name"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">BEAN_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"bean"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">META_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"meta"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">ID_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"id"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">PARENT_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"parent"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">CLASS_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"class"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">ABSTRACT_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"abstract"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">SCOPE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"scope"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">SINGLETON_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"singleton"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">LAZY_INIT_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"lazy-init"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"autowire"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">AUTOWIRE_CANDIDATE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"autowire-candidate"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">PRIMARY_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"primary"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEPENDENCY_CHECK_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"dependency-check"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DEPENDS_ON_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"depends-on"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">INIT_METHOD_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"init-method"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">DESTROY_METHOD_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"destroy-method"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">FACTORY_METHOD_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"factory-method"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">FACTORY_BEAN_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"factory-bean"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">CONSTRUCTOR_ARG_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"constructor-arg"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">INDEX_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"index"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">TYPE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"type"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">VALUE_TYPE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"value-type"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">KEY_TYPE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"key-type"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">PROPERTY_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"property"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">REF_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"ref"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">VALUE_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"value"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">LOOKUP_METHOD_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"lookup-method"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">REPLACED_METHOD_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"replaced-method"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">REPLACER_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"replacer"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">ARG_TYPE_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"arg-type"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">ARG_TYPE_MATCH_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">REF_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"ref"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">IDREF_ELEMENT</span> <span class="token operator">=</span> <span class="token string">"idref"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">BEAN_REF_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"bean"</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">LOCAL_REF_ATTRIBUTE</span> <span class="token operator">=</span> <span class="token string">"local"</span><span class="token punctuation">;</span>
    
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

看出来了么,主要都是 xml 里面的那些元素的名称。

里面的方法,很多,我们用到了再说。

我们继续回到主线任务:

Copy
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions /** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 一般来说,我们的根节点都是 <beans>,这个是默认 namespace 的 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍历 xml <beans> 下的每个元素 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; // 判断元素是不是默认命名空间的,比如 <bean> 是,<context:component-scan> 不是 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { //<context:component-scan>,<aop:xxxx> 走这边 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }

这里,判断一个元素是不是默认命名空间,具体怎么做的呢:

Copy
BeanDefinitionParserDelegate#isDefaultNamespace(org.w3c.dom.Node) public boolean isDefaultNamespace(Node node) { // 调用重载方法 return isDefaultNamespace(getNamespaceURI(node)); }

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

默认 namespace 时的逻辑#

Copy
DefaultBeanDefinitionDocumentReader#parseDefaultElement private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }

第一次发现,原来默认命名空间下,才这么几个元素:

import、alias、bean、(NESTED_BEANS_ELEMENT)beans

具体的解析放到下讲,这讲内容已经够多了。

非默认 namespace 时的逻辑#

主要是处理:aop、context等非默认namespace

Copy
BeanDefinitionParserDelegate public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); // 这里,就和前面串起来了,根据 namespace 找 handler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }

我们挑一个大家最熟悉的org.springframework.context.config.ContextNamespaceHandler,大家先看看:

Copy
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 这个也熟悉吧</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"property-placeholder"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">PropertyPlaceholderBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"property-override"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">PropertyOverrideBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"annotation-config"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">AnnotationConfigBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 这个熟悉吧</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"component-scan"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ComponentScanBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"load-time-weaver"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">LoadTimeWeaverBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"spring-configured"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">SpringConfiguredBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"mbean-export"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">MBeanExportBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">registerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token string">"mbean-server"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">MBeanServerBeanDefinitionParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

总之,到了这里,就是根据具体的元素,找对应的处理器了。这些都后面再说了。内容太多了。

总结#

大家可以回头再去看看第二章的整体流程,会不会清晰一些了呢?

主要是几个核心类:

XmlBeanDefinitionReader

BeanDefinitionDocumentReader

XmlReaderContext

namespaceHandlerResolver

BeanDefinitionParserDelegate

内容有点多,大家不要慌,我们后面还会进一步讲解的。觉得有帮助的话,点个赞哦。