[TOC]
1.IOC的理解
如何理解“控制反转”好呢?理解好它的关键在于我们需要回答如下四个问题:
- 谁控制谁:在传统的开发模式下,我们都是采用直接 new 一个对象的方式来创建对象,也就是说你依赖的对象直接由你自己控制,但是有了 IoC 容器后,则直接由 IoC 容器来控制。所以“谁控制谁”,当然是 IoC 容器控制对象
- 控制什么:控制对象。
- 为何是反转:没有 IoC 的时候我们都是在自己对象中主动去创建被依赖的对象,这是正转。但是有了 IoC 后,所依赖的对象直接由 IoC 容器创建后注入到被注入的对象中,依赖的对象由原来的主动获取变成被动接受,所以是反转。
- 哪些方面反转了:所依赖对象的获取被反转了。
在没有引入 IoC 的时候,被注入的对象直接依赖于被依赖的对象,有了 IoC 后,两者及其他们的关系都是通过 Ioc Service Provider 来统一管理维护的。被注入的对象需要什么,后者就会把相应的被依赖对象注入到被注入的对象中,从而达到 IoC Service Provider 为被注入对象服务的目的。
1 | ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); |
org.springframework.context.ApplicationContext
,这个就是大名鼎鼎的 Spring 容器,它叫做应用上下文,与我们应用息息相关。它继承 BeanFactory ,所以它是 BeanFactory 的扩展升级版,如果BeanFactory 是屌丝的话,那么 ApplicationContext 则是名副其实的高富帅。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:
IoC 咋一看还是挺简单的,无非就是将配置文件(暂且认为是 xml 文件)进行解析(分析 xml 谁不会啊),然后放到一个 Map 里面就差不多了,初看有道理,其实要面临的问题还是有很多的,下面就劳烦各位看客跟着 LZ 博客来一步一步揭开 Spring IoC 的神秘面纱。
2.统一资源加载策略
- FileSystemResource :对
java.io.File
类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。 - ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
- UrlResource :对
java.net.URL
类型资源的封装。内部委派 URL 进行具体的资源操作。 - ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
- InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。
1 | public abstract class AbstractResource implements Resource { |
1 | public interface ResourceLoader { |
getResource(String location) 支持以下模式的资源加载:
- URL位置资源,如 “file:C:/test.dat” 。
- ClassPath位置资源,如 “classpath:test.dat 。
- 相对路径资源,如 “WEB-INF/test.dat” ,此时返回的Resource 实例,根据实现不同而不同。
- getClassLoader()方法的主要实现是在其子类 DefaultResourceLoader 中实现,具体过程我们在分析 DefaultResourceLoader 时做详细说明。
1 | // DefaultResourceLoader.java |
3.加载 BeanDefinition
1 | ClassPathResource resource = new ClassPathResource("bean.xml"); // <1> |
这段代码是 Spring 中编程式使用 IoC 容器,通过这四段简单的代码,我们可以初步判断 IoC 容器的使用过程。
- 获取资源
- 获取 BeanFactory
- 根据新建的 BeanFactory 创建一个 BeanDefinitionReader 对象,该 Reader 对象为资源的解析器
- 装载资源
整个过程就分为三个步骤:资源定位、装载、注册,如下:
资源定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IoC 容器的第一步就是需要定位这个外部资源。在上一篇博客(《【死磕 Spring】—— IoC 之 Spring 统一资源加载策略》)已经详细说明了资源加载的过程。
装载。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition 。
- 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构
- 在配置文件中每一个
<bean>
都对应着一个 BeanDefinition 对象。
注册。向 IoC 容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistry 接口来实现的。在 IoC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。
- 在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用
#getBean(...)
方法,向容器索要 Bean 时。 - 当然我们可以通过设置预处理,即对某个 Bean 设置
lazyinit = false
属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
简单的说,上面步骤的结果是,XML Resource => XML Document => Bean Definition 。
- 在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用
3.1loadBeanDefinitions
1 | // XmlBeanDefinitionReader.java |
- 从指定的 xml 文件加载 Bean Definition ,这里会先对 Resource 资源封装成
org.springframework.core.io.support.EncodedResource
对象。这里为什么需要将 Resource 封装成 EncodedResource 呢?主要是为了对 Resource 进行编码,保证内容读取的正确性。 - 然后,再调用
#loadBeanDefinitions(EncodedResource encodedResource)
方法,执行真正的逻辑实现。
1 | /** |
<1>处,通过resourcesCurrentlyBeingLoaded.get()代码,来获取已经加载过的资源,然后将encodedResource加入其中,如果esourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。
- 为什么需要这么做呢?答案在
"Detected cyclic loading"
,避免一个 EncodedResource 在加载时,还没加载完成,又加载自身,从而导致死循环。 - 也因此,在
<3>
处,当一个 EncodedResource 加载完成后,需要从缓存中剔除。
- 为什么需要这么做呢?答案在
<2>
处理,从encodedResource
获取封装的 Resource 资源,并从 Resource 中获取相应的 InputStream ,然后将 InputStream 封装为 InputSource ,最后调用#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,执行加载 Bean Definition 的真正逻辑。
3.2 doLoadBeanDefinitions
1 | /** |
- 在
<1>
处,调用#doLoadDocument(InputSource inputSource, Resource resource)
方法,根据 xml 文件,获取 Document 实例。 - 在
<2>
处,调用#registerBeanDefinitions(Document doc, Resource resource)
方法,根据获取的 Document 实例,注册 Bean 信息。
3.2.1 doLoadDocument
1 | /** |
- 调用
#getValidationModeForResource(Resource resource)
方法,获取指定资源(xml)的验证模式。详细解析,见 《【死磕 Spring】—— IoC 之获取验证模型》 。 - 调用
DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
方法,获取 XML Document 实例。详细解析,见 《【死磕 Spring】—— IoC 之获取 Document 对象》
3.2.2 registerBeanDefinitions
该方法的详细解析,见 《【死磕 Spring】—— IoC 之注册 BeanDefinition》 。
4.获取验证模型
#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法中,中主要是做三件事情:
- 调用
#getValidationModeForResource(Resource resource)
方法,获取指定资源(xml)的验证模式。 - 调用
DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
方法,获取 XML Document 实例。 - 调用
#registerBeanDefinitions(Document doc, Resource resource)
方法,根据获取的 Document 实例,注册 Bean 信息。
DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。
1. getValidationModeForResource
1 | // XmlBeanDefinitionReader.java |
<1>
处,调用#getValidationMode()
方法,获取指定的验证模式(validationMode
)。如果有手动指定,则直接返回。<2>
处,调用#detectValidationMode(Resource resource)
方法,自动获取验证模式。代码如下:1
2
3
4
5
6
7
8
9
10
11
12/**
* XML 验证模式探测器
*/
...
// <x> 获取相应的验证模式
try {
return this.validationModeDetector.detectValidationMode(inputStream);
} catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}- 核心在于
<x>
处,调用XmlValidationModeDetector#detectValidationMode(InputStream inputStream)
方法,获取相应的验证模式。详细解析,见 「2. XmlValidationModeDetector」 中。
- 核心在于
<3>
处,使用VALIDATION_XSD
做为默认。
2. XmlValidationModeDetector
org.springframework.util.xml.XmlValidationModeDetector
,XML 验证模式探测器。
1 | public int detectValidationMode(InputStream inputStream) throws IOException { |
<0
> 处,从代码中看,主要是通过读取 XML 文件的内容,来进行自动判断。<1>
处,调用#hasDoctype(String content)
方法,判断内容中如果包含有"DOCTYPE
“ ,则为 DTD 验证模式。代码如下:1
2
3
4
5
6
7
8
9/**
* The token in a XML document that declares the DTD to use for validation
* and thus that DTD validation is being used.
*/
private static final String DOCTYPE = "DOCTYPE";
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}<2>
处,调用#hasOpeningTag(String content)
方法,判断如果这一行包含<
,并且<
紧跟着的是字幕,则为 XSD 验证模式。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* Does the supplied content contain an XML opening tag. If the parse state is currently
* in an XML comment then this method always returns false. It is expected that all comment
* tokens will have consumed for the supplied content before passing the remainder to this method.
*/
private boolean hasOpeningTag(String content) {
if (this.inComment) {
return false;
}
int openTagIndex = content.indexOf('<');
return (openTagIndex > -1 // < 存在
&& (content.length() > openTagIndex + 1) // < 后面还有内容
&& Character.isLetter(content.charAt(openTagIndex + 1))); // < 后面的内容是字幕
}<3>
处,如果发生 CharConversionException 异常,则为VALIDATION_AUTO
模式。关于
#consumeCommentTokens(String content)
方法,代码比较复杂。感兴趣的胖友可以看看。代码如下:
5.获取document对象
在 XmlBeanDefinitionReader#doLoadDocument(InputSource inputSource, Resource resource)
方法,中做了两件事情:
调用#getValidationModeForResource(Resource resource)方法,获取指定资源(xml)的验证模式
- 上篇博客,我们已经详细解析。
调用DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)方法,获取 XML Document 实例。
1. DocumentLoader
获取 Document 的策略,由接口 org.springframework.beans.factory.xml.DocumentLoader
定义。代码如下:
FROM 《Spring 源码深度解析》P16 页
定义从资源文件加载到转换为 Document 的功能。
1 | public interface DocumentLoader { |
inputSource
方法参数,加载 Document 的 Resource 资源。entityResolver
方法参数,解析文件的解析器。errorHandler
方法参数,处理加载 Document 对象的过程的错误。validationMode
方法参数,验证模式。namespaceAware
方法参数,命名空间支持。如果要提供对 XML 名称空间的支持,则需要值为true
。
1.1 DefaultDocumentLoader
该方法由 DocumentLoader 的默认实现类 org.springframework.beans.factory.xml.DefaultDocumentLoader
实现。代码如下:
1 | /** |
首先,调用 #
createDocumentBuilderFactory(...)
方法,创建javax.xml.parsers.DocumentBuilderFactory
对象。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/**
* JAXP attribute used to configure the schema language for validation.
*/
private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
/**
* JAXP attribute value indicating the XSD schema language.
*/
private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
// 创建 DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware); // 设置命名空间支持
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true); // 开启校验
// XSD 模式下,设置 factory 的属性
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true); // XSD 模式下,强制设置命名空间支持
// 设置 SCHEMA_LANGUAGE_ATTRIBUTE
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
} catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}然后,调用
#createDocumentBuilder(DocumentBuilderFactory factory, EntityResolver entityResolver,ErrorHandler errorHandler)
方法,创建javax.xml.parsers.DocumentBuilder
对象。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
// 创建 DocumentBuilder 对象
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// <x> 设置 EntityResolver 属性
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
// 设置 ErrorHandler 属性
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}- 在
<x>
处,设置 DocumentBuilder 的 EntityResolver 属性。关于它,在 「2. EntityResolver」 会详细解析。
- 在
最后,调用
DocumentBuilder#parse(InputSource)
方法,解析 InputSource ,返回 Document 对象。
2. EntityResolver
也就是说,对于解析一个xml,sax
首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,
默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),
并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是因为相应的dtd没找到,
通过 DocumentLoader#loadDocument(...)
方法来获取 Document 对象时,有一个方法参数 entityResolver
。该参数是通过 XmlBeanDefinitionReader#getEntityResolver()
方法来获取的。代码如下:
#getEntityResolver()
方法,返回指定的解析器,如果没有指定,则构造一个未指定的默认解析器。
1 | // XmlBeanDefinitionReader.java |
- 如果 ResourceLoader 不为
null
,则根据指定的 ResourceLoader 创建一个 ResourceEntityResolver 对象。 - 如果 ResourceLoader 为
null
,则创建 一个 DelegatingEntityResolver 对象。该 Resolver 委托给默认的 BeansDtdResolver 和 PluggableSchemaResolver 。
2.1 子类
上面的方法,一共涉及四个 EntityResolver 的子类:
org.springframework.beans.factory.xm.BeansDtdResolver
:实现 EntityResolver 接口,Spring Bean dtd 解码器,用来从 classpath 或者 jar 文件中加载 dtd 。部分代码如下:1
2
3private static final String DTD_EXTENSION = ".dtd";
private static final String DTD_NAME = "spring-beans";org.springframework.beans.factory.xml.PluggableSchemaResolver
,实现 EntityResolver 接口,读取 classpath 下的所有"META-INF/spring.schemas"
成一个 namespaceURI 与 Schema 文件地址的 map 。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* The location of the file that defines schema mappings.
* Can be present in multiple JAR files.
*
* 默认 {@link #schemaMappingsLocation} 地址
*/
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
@Nullable
private final ClassLoader classLoader;
/**
* Schema 文件地址
*/
private final String schemaMappingsLocation;
/** Stores the mapping of schema URL -> local schema path. */
@Nullable
private volatile Map<String, String> schemaMappings; // namespaceURI 与 Schema 文件地址的映射集合org.springframework.beans.factory.xml.DelegatingEntityResolver
:实现 EntityResolver 接口,分别代理 dtd 的 BeansDtdResolver 和 xml schemas 的 PluggableSchemaResolver 。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/** Suffix for DTD files. */
public static final String DTD_SUFFIX = ".dtd";
/** Suffix for schema definition files. */
public static final String XSD_SUFFIX = ".xsd";
private final EntityResolver dtdResolver;
private final EntityResolver schemaResolver;
// 默认
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
// 自定义
public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
Assert.notNull(dtdResolver, "'dtdResolver' is required");
Assert.notNull(schemaResolver, "'schemaResolver' is required");
this.dtdResolver = dtdResolver;
this.schemaResolver = schemaResolver;
}org.springframework.beans.factory.xml.ResourceEntityResolver
:继承自 DelegatingEntityResolver 类,通过 ResourceLoader 来解析实体的引用。代码如下:1
2
3
4
5
6private final ResourceLoader resourceLoader;
public ResourceEntityResolver(ResourceLoader resourceLoader) {
super(resourceLoader.getClassLoader());
this.resourceLoader = resourceLoader;
}
2.2 作用
EntityResolver 的作用就是,通过实现它,应用可以自定义如何寻找【验证文件】的逻辑。
FROM 《Spring 源码深度解析》
在 loadDocument 方法中涉及一个参数 EntityResolver ,何为EntityResolver?官网这样解释:如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向SAX 驱动器注册一个实例。也就是说,对于解析一个XML,SAX 首先读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因。
EntityResolver 的作用是项目本身就可以提供一个如何寻找 DTD 声明的方法,即由程序来实现寻找 DTD 声明的过程,比如我们将 DTD 文件放到项目中某处,在实现时直接将此文档读取并返回给 SAX 即可。这样就避免了通过网络来寻找相应的声明。
org.xml.sax.EntityResolver
接口,代码如下:
1 | public interface EntityResolver { |
接口方法接收两个参数 publicId
和 systemId
,并返回 InputSource 对象。两个参数声明如下:
publicId
:被引用的外部实体的公共标识符,如果没有提供,则返回null
。systemId
:被引用的外部实体的系统标识符。
这两个参数的实际内容和具体的验证模式的关系如下:
- XSD 验证模式
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
- XSD 验证模式
- DTD 验证模式
- publicId:-//SPRING//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/dtd/spring-beans.dtd
- DTD 验证模式
2.3 DelegatingEntityResolver
我们知道在 Spring 中使用 DelegatingEntityResolver 为 EntityResolver 的实现类。#resolveEntity(String publicId, String systemId)
方法,实现如下:
1 | @Override |
- 如果是 DTD 验证模式,则使用 BeansDtdResolver 来进行解析
- 如果是 XSD 验证模式,则使用 PluggableSchemaResolver 来进行解析。
2.4 BeansDtdResolver
BeansDtdResolver 的解析过程,代码如下:
1 | /** |
从上面的代码中,我们可以看到,加载 DTD 类型的 BeansDtdResolver#resolveEntity(...)
过程,只是对 systemId
进行了简单的校验(从最后一个 / 开始,内容中是否包含 spring-beans
),然后构造一个 InputSource 对象,并设置 publicId
、systemId
属性,然后返回。
2.5 PluggableSchemaResolver
PluggableSchemaResolver 的解析过程,代码如下:
1 | @Nullable |
首先调用
#getSchemaMappings()
方法,获取一个映射表(systemId 与其在本地的对照关系)。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29private Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
// 双重检查锁,实现 schemaMappings 单例
if (schemaMappings == null) {
synchronized (this) {
schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
// 以 Properties 的方式,读取 schemaMappingsLocation
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded schema mappings: " + mappings);
}
// 将 mappings 初始化到 schemaMappings 中
schemaMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
} catch (IOException ex) {
throw new IllegalStateException(
"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
}
}
}
}
return schemaMappings;
}然后,根据传入的
systemId
获取该systemId
在本地的路径resourceLocation
。最后,根据
resourceLocation
,构造 InputSource 对象。
2.6 ResourceEntityResolver
ResourceEntityResolver 的解析过程,代码如下:
1 | private final ResourceLoader resourceLoader; |
- 首先,调用父类的方法,进行解析。
- 如果失败,使用
resourceLoader
,尝试读取systemId
对应的 Resource 资源。
2.7 自定义 EntityResolver
老艿艿:本小节,为选读内容。
#getEntityResolver()
方法返回 EntityResolver 对象。那么怎么进行自定义 EntityResolver 呢?
If a SAX application needs to implement customized handling for external entities, it must implement this interface and register an instance with the SAX driver using the setEntityResolver method.
就是说:如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口,并使用
#setEntityResolver(EntityResolver entityResolver)
方法,向 SAX 驱动器注册一个 EntityResolver 实例。
示例如下:
1 | public class MyResolver implements EntityResolver { |
我们首先将 "spring-student.xml"
文件中的 XSD 声明的地址改掉,如下:
如果我们再次运行,则会报如下错误:
从上面的错误可以看到,是在进行文档验证时,无法根据声明找到 XSD 验证文件而导致无法进行 XML 文件验证。在 《【死磕 Spring】—— IoC 之获取验证模型》 中讲到,如果要解析一个 XML 文件,SAX 首先会读取该 XML 文档上的声明,然后根据声明去寻找相应的 DTD 定义,以便对文档进行验证。默认的加载规则是通过网络方式下载验证文件,而在实际生产环境中我们会遇到网络中断或者不可用状态,那么就应用就会因为无法下载验证文件而报错。
6.注册 BeanDefinitions
获取 XML Document 对象后,会根据该对象和 Resource 资源对象调用 XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)
方法,开始注册 BeanDefinitions 之旅。代码如下:
1 | // AbstractBeanDefinitionReader.java |
<1>
处,调用#createBeanDefinitionDocumentReader()
方法,实例化 BeanDefinitionDocumentReader 对象。
FROM 《Spring 源码深度解析》P16 页
定义读取 Document 并注册 BeanDefinition 功能
<2>
处,调用BeanDefinitionRegistry#getBeanDefinitionCount()
方法,获取已注册的 BeanDefinition 数量。<3>
处,调用#createReaderContext(Resource resource)
方法,创建 XmlReaderContext 对象。<4>
处,调用BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
方法,读取 XML 元素,注册 BeanDefinition 们。<5>
处,计算新注册的 BeanDefinition 数量。
1. createBeanDefinitionDocumentReader
#createBeanDefinitionDocumentReader()
,实例化 BeanDefinitionDocumentReader 对象。代码如下:
1 | /** |
documentReaderClass
的默认值为DefaultBeanDefinitionDocumentReader.class
。关于它,我们在后续的文章,详细解析。
2. registerBeanDefinitions
BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
方法,注册 BeanDefinition ,在接口 BeanDefinitionDocumentReader 中定义。代码如下:
1 | public interface BeanDefinitionDocumentReader { |
从给定的 Document 对象中解析定义的 BeanDefinition 并将他们注册到注册表中。方法接收两个参数:
doc
方法参数:待解析的 Document 对象。readerContext
方法,解析器的当前上下文,包括目标注册表和被解析的资源。它是根据 Resource 来创建的,见 「3. createReaderContext」 。
2.1 DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader 有且只有一个默认实现类 DefaultBeanDefinitionDocumentReader 。它对 #registerBeanDefinitions(...)
方法的实现代码如下:
DefaultBeanDefinitionDocumentReader 对该方法提供了实现:
1 | @Nullable |
<1>
处,创建 BeanDefinitionParserDelegate 对象,并进行设置到delegate
。BeanDefinitionParserDelegate 是一个重要的类,它负责解析 BeanDefinition。代码如下:FROM 《Spring 源码深度解析》P16
定义解析 XML Element 的各种方法
1
2
3
4
5
6
7
8protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
// 创建 BeanDefinitionParserDelegate 对象
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
// 初始化默认
delegate.initDefaults(root, parentDelegate);
return delegate;
}<2>
处,检查<beans />
根标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans 。<2.1>
处,判断是否<beans />
上配置了profile
属性。不了解这块的胖友,可以看下 《《Spring3自定义环境配置 》》 。<2.2>
处,使用分隔符切分,可能有多个 profile 。<2.3>
处,判断,如果所有 profile 都无效,则return
不进行注册。
<4>
处,调用#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
方法,进行解析逻辑。详细解析,见 「3.1 parseBeanDefinitions」 。<3>
/<5>
处,解析前后的处理,目前这两个方法都是空实现,交由子类来实现。代码如下:1
2
3protected void preProcessXml(Element root) {}
protected void postProcessXml(Element root) {}
2.1.1 parseBeanDefinitions
#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
方法,进行解析逻辑。代码如下:
1 | /** |
Spring 有
两种
Bean 声明方式:
- 配置文件式声明:
<bean id="studentService" class="org.springframework.core.StudentService" />
。对应<1>
处。 - 自定义注解方式:
<tx:annotation-driven>
。对应<2>
处。
<1>
处,如果根节点或子节点使用默认命名空间,调用#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法,执行默认解析。代码如下:1
2
3
4
5
6
7
8
9
10
11
12private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean
processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans
// recurse
doRegisterBeanDefinitions(ele);
}
}- 详细的解析,见后续文章。
<2>
处,如果根节点或子节点不使用默认命名空间,调用BeanDefinitionParserDelegate#parseCustomElement(Element ele)
方法,执行自定义解析。详细的解析,见后续文章。
3. createReaderContext
#createReaderContext(Resource resource)
方法,创建 XmlReaderContext 对象。代码如下:
1 | private ProblemReporter problemReporter = new FailFastProblemReporter(); |
关于 XmlReaderContext 的详细解析,见后续文章。
4. 小结
至此,XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法中,做的三件事情已经全部分析完毕,下面将对 BeanDefinition 的解析过程做详细分析说明。
另外,XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,整体时序图如下:
7.解析 import 标签
在博客 【死磕 Spring】—— IoC 之注册 BeanDefinitions 中分析到,Spring 中有两种解析 Bean 的方式:
- 如果根节点或者子节点采用默认命名空间的话,则调用
#parseDefaultElement(...)
方法,进行默认标签解析 - 否则,调用
BeanDefinitionParserDelegate#parseCustomElement(...)
方法,进行自定义解析。
所以,以下博客就这两个方法进行详细分析说明。而本文,先从默认标签解析过程开始。代码如下:
1 | // DefaultBeanDefinitionDocumentReader.java |
该方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 import
、alias
、bean
、beans
。咱门从第一个标签 import
开始。
1. import 示例
经历过 Spring 配置文件的小伙伴都知道,如果工程比较大,配置文件的维护会让人觉得恐怖,文件太多了,想象将所有的配置都放在一个 spring.xml
配置文件中,哪种后怕感是不是很明显?
所有针对这种情况 Spring 提供了一个分模块的思路,利用 import
标签,例如我们可以构造一个这样的 spring.xml
。
1 | <?xml version="1.0" encoding="UTF-8"?> |
spring.xml
配置文件中,使用 import
标签的方式导入其他模块的配置文件。
- 如果有配置需要修改直接修改相应配置文件即可。
- 若有新的模块需要引入直接增加
import
即可。
这样大大简化了配置后期维护的复杂度,同时也易于管理。
2. importBeanDefinitionResource
Spring 使用 #importBeanDefinitionResource(Element ele)
方法,完成对 import
标签的解析。
1 | // DefaultBeanDefinitionDocumentReader.java |
解析 import
标签的过程较为清晰,整个过程如下:
<1>
source1
2
3
4
处,获取<3>1
2
3
4
5
6
7
8
属性的值,该值表示资源的路径。
- `<2>` 处,解析路径中的系统属性,如 `"${user.dir}"` 。
-location1
2
3
4
处,判断资源路径absoluteLocation = ResourcePatternUtils.isUrl(location) // <1> || ResourceUtils.toURI(location).isAbsolute(); // <2>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
是绝对路径还是相对路径。详细解析,见
「2.1 判断路径」
。
- `<4>` 处,如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析。详细解析,见 [「2.2 处理绝对路径」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-import/#) 。
- `<5>` 处,如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析。详细解析,见 [「2.3 处理相对路径」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-import/#) 。
- `<6>` 处,通知监听器,完成解析。
#### 2.1 判断路径
通过以下代码,来判断 `location` 是为相对路径还是绝对路径:/** * Load bean definitions from the specified resource location. * <p>The location can also be a location pattern, provided that the * ResourceLoader of this bean definition reader is a ResourcePatternResolver. * @param location the resource location, to be loaded with the ResourceLoader * (or ResourcePatternResolver) of this bean definition reader * @param actualResources a Set to be filled with the actual Resource objects * that have been resolved during the loading process. May be {@code null} * to indicate that the caller is not interested in those Resource objects. * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #getResourceLoader() * @see #loadBeanDefinitions(org.springframework.core.io.Resource) * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) */ public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // 获得 ResourceLoader 对象 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { // 获得 Resource 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。例如说,Ant 风格的 location Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 加载 BeanDefinition 们 int count = loadBeanDefinitions(resources); // 添加到 actualResources 中 if (actualResources != null) { Collections.addAll(actualResources, resources); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. // 获得 Resource 对象, Resource resource = resourceLoader.getResource(location); // 加载 BeanDefinition 们 int count = loadBeanDefinitions(resource); // 添加到 actualResources 中 if (actualResources != null) { actualResources.add(resource); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } }1
2
3
4
5
6
7
8
9
10
判断绝对路径的规则如下:
- `<1>` 以 `classpath*:` 或者 `classpath:` 开头的为绝对路径。
- `<1>` 能够通过该 `location` 构建出 `java.net.URL` 为绝对路径。
- `<2>` 根据 `location` 构造 `java.net.URI` 判断调用 `#isAbsolute()` 方法,判断是否为绝对路径。
#### 2.2 处理绝对路径
如果 `location` 为绝对路径,则调用 `#loadBeanDefinitions(String location, Set<Resource> actualResources)`, 方法。该方法在 `org.springframework.beans.factory.support.AbstractBeanDefinitionReader` 中定义,代码如下:// DefaultBeanDefinitionDocumentReader.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
整个逻辑比较简单:
- 首先,获取 ResourceLoader 对象。
- 然后,根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource 。
- 最终,都会回归到 `XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources)` 方法,所以这是一个递归的过程。
- 另外,获得到的 Resource 的对象或数组,都会添加到 `actualResources` 中。
#### 2.3 处理相对路径
如果 `location` 是相对路径,则会根据相应的 Resource 计算出相应的相对路径的 Resource 对象 ,然后:
- 若该 Resource 存在,则调用 `XmlBeanDefinitionReader#loadBeanDefinitions()` 方法,进行 BeanDefinition 加载。
- 否则,构造一个绝对 `location`( 即 `StringUtils.applyRelativePath(baseLocation, location)` 处的代码),并调用 `#loadBeanDefinitions(String location, Set<Resource> actualResources)` 方法,**与绝对路径过程一样**。
### 3. 小结
至此,`import` 标签解析完毕,整个过程比较清晰明了:**获取 source 属性值,得到正确的资源路径,然后调用 `XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources)` 方法,进行递归的 BeanDefinition 加载**。
## 8. IoC 之解析 <bean> 标签:开启解析进程
`import` 标签解析完毕了,我们一起来看看 Spring 中**最复杂也是最重要**的标签 `bean` 标签的解析过程。
### 1. processBeanDefinition
在方法 `#parseDefaultElement(...)` 方法中,如果遇到标签为 `bean` 时,则调用 `#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)` 方法,进行 `bean` 标签的解析。代码如下:
/**
- Process the given bean element, parsing the bean definition
- and registering it with the registry.
- /
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 进行 bean 元素解析。
// <1> 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
// 如果解析失败,则返回 null 。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
}// <2> 进行自定义标签处理 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // <3> 进行 BeanDefinition 的注册 // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // <4> 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。 // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate)1
2
3
4
整个过程分为四个步骤:
1. 调用// BeanDefinitionParserDelegate.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
方法,进行元素解析。
- 如果解析**失败**,则返回 `null`,错误由 ProblemReporter 处理。
- 如果解析**成功**,则返回 BeanDefinitionHolder 实例 `bdHolder` 。BeanDefinitionHolder 为持有 `name` 和 `alias` 的 BeanDefinition。
- 详细解析,见 [「2. parseBeanDefinitionElement」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-in-processBeanDefinition/#) 。
2. 若实例 `bdHolder` 不为空,则调用 `BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder bdHolder)` 方法,进行自定义标签处理。
3. 解析完成后,则调用 `BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)` 方法,对 `bdHolder` 进行 BeanDefinition 的注册。
4. 发出响应事件,通知相关的监听器,完成 Bean 标签解析。
### 2. parseBeanDefinitionElement
`BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate)` 方法,进行 `<bean>` 元素解析。代码如下:
/**
- Parses the supplied {@code
} element. May return {@code null} - if there were errors during parse. Errors are reported to the
- {@link org.springframework.beans.factory.parsing.ProblemReporter}.
- /
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
/**
Parses the supplied {@code
} element. May return {@code null} if there were errors during parse. Errors are reported to the
{@link org.springframework.beans.factory.parsing.ProblemReporter}.
@param containingBean TODO 芋艿,需要进一步确认
/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// <1> 解析 id 和 name 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// <1> 计算别名集合
Listaliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr));
}
// <3.1> beanName ,优先,使用 id
String beanName = id;
// <3.2> beanName ,其次,使用 aliases 的第一个
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"); }
}
// <2> 检查 beanName 的唯一性
if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);
}
// <4> 解析属性,构造 AbstractBeanDefinition 对象
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {// <3.3> beanName ,再次,使用 beanName 生成规则 if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { // <3.3> 生成唯一的 beanName beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { // <3.3> 生成唯一的 beanName beanName = this.readerContext.generateBeanName(beanDefinition); // TODO 芋艿,需要进一步确认 // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isTraceEnabled()) { logger.trace("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } // <5> 创建 BeanDefinitionHolder 对象 String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}1
2
3
4
5
6
这个方法还没有对 `bean` 标签进行解析,只是在解析动作之前做了一些功能架构,主要的工作有:
- `<1>` 处,解析 `id`、`name` 属性,确定 `aliases` 集合
- `<2>` 处,检测 `beanName` 是否唯一。代码如下:/**
- 已使用 Bean 名字的集合
- Stores all used bean names so we can enforce uniqueness on a per
- beans-element basis. Duplicate bean ids/names may not exist within the
- same level of beans element nesting, but may be duplicated across levels.
- /
private final SetusedNames = new HashSet<>();
/**
Validate that the specified bean name and aliases have not been used already
within the current level of beans element nesting.
/
protected void checkNameUniqueness(String beanName, Listaliases, Element beanElement) {
// 寻找是否 beanName 已经使用
String foundName = null;
if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {foundName = beanName;
}
if (foundName == null) {foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
}
// 若已使用,使用 problemReporter 提示错误
if (foundName != null) {error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
}
// 添加到 usedNames 集合
this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}1
2
3
4
5
6
7
8
9
10
- 这里有必要说下 `beanName` 的命名规则:
- `<3.1>` 处,如果 `id` 不为空,则 `beanName = id` 。
- `<3.2>` 处,如果 `id` 为空,但是 `aliases` 不空,则 `beanName` 为 `aliases` 的**第一个**元素
- `<3.3>` 处,如果两者都为空,则根据**默认规则**来设置 beanName 。因为**默认规则**不是本文的重点,所以暂时省略。感兴趣的胖友,自己研究下哈。
- `<4>` 处,调用 `#parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)` 方法,对属性进行解析并封装成 AbstractBeanDefinition 实例 `beanDefinition` 。详细解析,见 [「2.1 parseBeanDefinitionElement」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-in-processBeanDefinition/#) 。
- `<5>` 处,根据所获取的信息(`beanName`、`aliases`、`beanDefinition`)构造 BeanDefinitionHolder 实例对象并返回。其中,BeanDefinitionHolder 的简化代码如下:/**
BeanDefinition 对象
/
private final BeanDefinition beanDefinition;
/**Bean 名字
/
private final String beanName;
/**别名集合
/
@Nullable
private final String[] aliases;1
2
3
4
5
6
> TODO 芋艿,需要进一步确认,未来参考下 [《Spring专题之IOC源码分析》](https://segmentfault.com/a/1190000016261917) ,进行细化。
#### 2.1 parseBeanDefinitionElement
`#parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)` 方法,对属性进行解析并封装成 AbstractBeanDefinition 实例,代码如下:/**
Parse the bean definition itself, without regard to name or aliases. May return
{@code null} if problems occurred during the parsing of the bean definition.
/
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
// 解析 class 属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
// 解析 parent 属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 创建用于承载属性的 AbstractBeanDefinition 实例 AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 解析默认 bean 的各种属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 提取 description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // tips: // 下面的一堆是解析 <bean>......</bean> 内部的子元素, // 解析出来以后的信息都放到 bd 的属性中 // 解析元数据 <meta /> parseMetaElements(ele, bd); // 解析 lookup-method 属性 <lookup-method /> parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析 replaced-method 属性 <replaced-method /> parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析构造函数参数 <constructor-arg /> parseConstructorArgElements(ele, bd); // 解析 property 子元素 <property /> parsePropertyElements(ele, bd); // 解析 qualifier 子元素 <qualifier /> 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;
}1
2
3
4
5
6
到这里,`bean` 标签的所有属性我们都可以看到其解析的过程,也就说到这里我们已经解析一个基本可用的 BeanDefinition 。
#### 2.2 createBeanDefinition
`#createBeanDefinition(String className, String parentName)` 方法,创建 AbstractBeanDefinition 对象。代码如下:/**
Create a bean definition for the given class name and parent name.
@param className the name of the bean class
@param parentName the name of the bean’s parent bean
@return the newly created bean definition
@throws ClassNotFoundException if bean class resolution was attempted but failed
/
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}
3. 小节
由于解析过程 bean
标签的属性较为漫长,篇幅较大,为了更好的观看体验,将这篇博文进行拆分。
下篇博客主要介绍 BeanDefinition ,以及解析默认 bean
标签的各种属性的过程,即 #parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd)
方法。