Spring IoC 源码解析:简单容器的初始化过程

本文将主要对定义在 XML 文件中的 bean 从静态配置到加载成为可使用对象的过程,即 IoC 容器的初始化过程进行一个整体的分析。在讲解上不主张对各个组件进行深究,只求对简单容器的实现有一个整体的认识,具体实现细节留到后面专门用针对性的篇章进行讲解。

首先我们引入一个 Spring 入门示例,假设我们现在定义了一个类 MyBean,我们希望利用 Spring 管理类对象。这里我们采用 Spring 经典的 XML 配置文件形式进行配置:

1
<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean"/>

我们将配置文件命名为 spring-core.xml,获取 bean 实例最原始的方式如下:

1
2
3
4
5
6
// 1. 定义资源描述
Resource resource = new ClassPathResource("spring-core.xml");
// 2. 基于 XmlBeanFactory 初始化 IoC 容器
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
// 3. 从 IoC 容器中加载获取 bean 实例
MyBean myBean = (MyBean) beanFactory.getBean("myBean");

上述示例虽然简单,但麻雀虽小,五脏俱全,完整的让 Spring 执行了一遍加载配置文件,创建并初始化 bean 实例的过程。虽然从 Spring 3.1 版本开始,XmlBeanFactory 已经被置为 deprecated,但是 Spring 并没有定义出更加高级的基于 XML 加载 bean 实例的 BeanFactory,而是推荐采用更加原生的方式,即组合使用 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 来完成上述过程:

1
2
3
4
5
Resource resource = new ClassPathResource("spring-core.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");

XmlBeanFactory 实际上是对 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 组合使用方式的封装,并没有增加新的处理逻辑。考虑到使用习惯,我们仍将继续基于 XmlBeanFactory 分析 bean 的加载过程。

Bean 的加载过程整体上可以分成两步:

  1. 完成由静态配置到内存表示 BeanDefinition 的转换;
  2. 基于 BeanDefinition 实例创建并初始化 bean 实例。

我们将第一步称为 bean 的解析与注册的过程,解析配置并注册到容器;将第二步看作是 bean 的创建和初始化的过程。

资源的描述与加载

如上面的例子所示,在加载配置文件之前,Spring 都会将配置文件封装成 Resource 对象。Resource 本身是一个接口,是对资源描述符的一种抽象。资源(File、URL、Classpath 等等)是众多框架使用和运行的基础,Spring 当然也不例外,框架诞生之初就是基于 XML 文件对 bean 进行配置。在开始分析容器的初始化过程之前,我们先来对支撑容器运行的 Resource 接口及其实现类做一个简单的了解。

资源的抽象声明

资源在 java 中被抽象成 URL,通过注册相应的 handler 来处理不同资源的操作逻辑,而 Spring 则采用 Resource 接口对各种资源进行统一抽象。Resource 接口声明了针对资源的基本操作,包括是否存在、是否可读,以及是否已经打开等等。Resource 接口实现如下:

1
2
3
4
public interface InputStreamSource {
/** 返回一个新的输入流 */
InputStream getInputStream() throws IOException;
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public interface Resource extends InputStreamSource {
/** 资源是否存在 */
boolean exists();

/** 资源是否可读 */
default boolean isReadable() {
return exists();
}

/** 资源流是否已经打开 */
default boolean isOpen() {
return false;
}

/** 是否是 File 对象 */
default boolean isFile() {
return false;
}

/** 返回资源对应的 URL 对象 */
URL getURL() throws IOException;

/** 返回资源对应的 URI 对象 */
URI getURI() throws IOException;

/** 返回资源对应的 File 对象 */
File getFile() throws IOException;

/** 返回资源对应的 ReadableByteChannel 对象 */
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}

/** 返回文件的长度 */
long contentLength() throws IOException;

/** 返回文件上次被修改的时间戳 */
long lastModified() throws IOException;

/** 依据当前资源创建一个相对的资源,并返回资源对象 */
Resource createRelative(String relativePath) throws IOException;

/** 返回资源的文件名 */
String getFilename();

/** 返回资源的描述信息 */
String getDescription();
}

由继承关系可以看到 Resource 继承了 InputStreamSource 接口,该接口描述任何可以返回 InputStream 的类,通过 InputStreamSource#getInputStream 方法获取对应的 InputStream 对象。

Resource 本身则声明了针对资源的基本操作,Spring 也针对不同类型的资源定义了相应的类实现,比如:文件(FileSystemResource)、字节数组资源(ByteArrayResource)、ClassPath 路径资源(ClassPathResource),以及 URL 资源(UrlResource)等,如下图所示(仅包含 IoC 层面的 Resource 定义):

image

资源的具体定义

参考上述 UML 图,可以将 Resource 的定义分为三层,其中第 1 层是 Resource 接口定义;第 2 层是对 Resource 接口的扩展,包括 AbstractResource 抽象类、WritableResource 接口,以及 ContextResource 接口;第 3 层是具体的针对不同类型资源的 Resource 实现类。

关于第 1 层 Resource 接口的定义已经在上一小节进行了说明,下面来简单介绍一下第 2 层和第 3 层中的 Resource 的定义。首先来看一下 第 2 层 ,包括:

  • WritableResource

WritableResource 接口用于描述一个资源是否支持可写的特性。在 Resource 接口定义中仅描述了一个资源是否可读,因为可读相对于可写是更加基本的特性,而对于可读又可写的文件来说,可以使用 WritableResource 接口予以描述。该接口声明了 3 个方法,其中 WritableResource#isWritable 方法用于判断文件是否可写;方法 WritableResource#getOutputStream 用于获取可写文件的 OutputStream 对象;方法 WritableResource#writableChannel 用于获取可写文件的 WritableByteChannel 对象。

  • ContextResource

ContextResource 是在 2.5 版本引入的一个扩展接口,用于描述从上下文环境中加载的资源,该接口仅声明了一个方法 ContextResource#getPathWithinContext,用于获取上下文环境的相对路径。

  • AbstractResource

AbstractResource 抽象类不是对某一具体资源的描述,而是一种编程技巧。Resource 接口中声明了资源的多种操作方法,如果我们直接去实现 Resource 接口,势必要提供针对每一个方法的实现,而这些方法可能并不需要全部提供支持。AbstractResource 抽象类对所有方法提供了默认实现,通过继承 AbstractResource 抽象类可以针对性的选择实现相应的方法。

下面来看一下 第 3 层 Resource 定义,这一层针对不同的资源类型定义了相应的 Resource 实现,这些实现类均派生自 AbstractResource 抽象类,其中一部分实现了 WritableResource 接口或 ContextResource 接口。

  • AbstractFileResolvingResource

AbstractFileResolvingResource 抽象了解析 URL 所指代的文件为 File 对象的过程,具体的实现典型的有 UrlResource 和 ClassPathResource。AbstractFileResolvingResource 抽象类定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class AbstractFileResolvingResource extends AbstractResource {
/** 解析 URL 所指向的 File 对象 */
public File getFile() throws IOException;
/** 解析 URI 所指向的 File 对象 */
protected File getFile(URI uri) throws IOException;
/** 解析 URL 所指向的底层文件为 File 对象,比如压缩包中的文件 */
protected File getFileForLastModifiedCheck() throws IOException;
public boolean exists()
public boolean isReadable();
public boolean isFile();
public long contentLength() throws IOException;
public long lastModified() throws IOException;
public ReadableByteChannel readableChannel() throws IOException;
}

我们来看一下 AbstractFileResolvingResource#getFileAbstractFileResolvingResource#getFileForLastModifiedCheck 方法的实现:

1
2
3
4
5
6
7
8
9
public File getFile() throws IOException {
// 获取 URL 对象
URL url = getURL();
// 如果是 JBoss VFS 文件
if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
return VfsResourceDelegate.getResource(url).getFile();
}
return ResourceUtils.getFile(url, getDescription());
}

上述方法用于解析 URL 所指向的文件为 File 对象,首先调用 AbstractResource#getURL 方法获取 URL 对象,然后检查当前 URL 是不是 JBoss VFS 文件,如果是则走 VFS 文件解析策略,否则调用工具类方法 ResourceUtils#getFile 进行解析,过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
Assert.notNull(resourceUrl, "Resource URL must not be null");
// URL 不是 file 协议,说明不是指代文件
if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
throw new FileNotFoundException(
description + " cannot be resolved to absolute file path " +
"because it does not reside in the file system: " + resourceUrl);
}
try {
// 由 URL 对象构造 File 对象
return new File(toURI(resourceUrl).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new File(resourceUrl.getFile());
}
}

方法 AbstractFileResolvingResource#getFileForLastModifiedCheck 相对于上述方法提供了对压缩文件 URL 路径的解析,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected File getFileForLastModifiedCheck() throws IOException {
// 获取 URL 对象
URL url = getURL();
// 如果 URL 的协议是 jar、war、zip、vfszip 或 wsjar 之一,则执行解析
if (ResourceUtils.isJarURL(url)) {
URL actualUrl = ResourceUtils.extractArchiveURL(url);
// 如果是 JBoss VFS 文件
if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
return VfsResourceDelegate.getResource(actualUrl).getFile();
}
return ResourceUtils.getFile(actualUrl, "Jar URL");
} else {
// 走普通的解析逻辑
return getFile();
}
}

方法首先获取 URL 对象,然后判断是不是压缩文件 URL,如果不是就走前面的 AbstractFileResolvingResource#getFile 进行常规解析;否则,即当前 URL 的协议是 jar、war、zip、vfszip 或 wsjar 中的一个,则首先解析 URL 得到常规 URL 对象,然后执行与 AbstractFileResolvingResource#getFile 方法相同的逻辑。

针对 AbstractFileResolvingResource 主要由两个直接实现类,即 UrlResource 和 ClassPathResource。其中 UrlResource 主要是解析 file: 协议;而 ClassPathResource 主要是对类上下文环境中资源的描述,基于 ClassLoader 或 Class 来定位加载资源。

  • FileSystemResource

FileSystemResource 是对文件系统类型资源的描述,这也是 Spring 中典型的资源类型。该类继承自 AbstractResource,并实现了 WritableResource 接口。

FileSystemResource 提供了两个构造方法分别由 File 对象和文件路径来构造资源对象,对于传入的路径,考虑输入的不确定性会执行 StringUtils#cleanPath 方法对其进行格式化。FileSystemResource 中的方法实现几乎都依赖于 File 类的 API。这里提一下 FileSystemResource#createRelative 方法,该方法会基于相对路径创建 FileSystemResource 对象,实现如下:

1
2
3
4
5
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return (this.file != null ? new FileSystemResource(pathToUse) :
new FileSystemResource(this.filePath.getFileSystem(), pathToUse));
}

首先利用 StringUtils#applyRelativePath 方法创建资源绝对路径,主要操作是截取 path 的最后一个文件分隔符 / 前面的内容与 relativePath 进行拼接,然后基于新的路径构造 FileSystemResource 对象。

  • PathResource

PathResource 在 4.0 版本引入的基于 JDK 7 NIO 2.0 中的 Path 类所实现的资源类型。NIO 2.0 针对本地 I/O 引入了许多新的类,用来改变 java 语言在 I/O 方面一直被人诟病的慢特性,所以 PathResource 也表示 Spring 由 BIO 向 NIO 的迈进。

  • DescriptiveResource

DescriptiveResource 资源并非表示一个真实可读的资源,而是对文件的一种描述,所以这类资源的 DescriptiveResource#exists 方法始终返回 false。这类资源的作用在于必要的时候用来占坑,例如文档所说的,当一个方法需要你传递一个资源对象,但又不会在方法中真正读取该对象的时候,如果没有合适的资源对象作为参数,就创建一个 DescriptiveResource 资源做参数吧。

  • BeanDefinitionResource

BeanDefinitionResource 是对 BeanDefinition 对象的一个包装。上一篇我们曾介绍过 BeanDefinition 对象是 Spring 核心类之一,是对 bean 定义在 IoC 容器内部进行表示的数据结构,我们在配置文件中定义的 bean,经过加载之后都会以 BeanDefinition 对象的形式存储在 IoC 容器中。BeanDefinitionResource 在实现上仅仅是持有 BeanDefinition 对象,并提供 getter 方法,而一般资源操作方法几乎都不支持。

  • ByteArrayResource

ByteArrayResource 利用字节数组作为资源存储的标的,JDK 原生也提供了字节数组式的 I/O 流,所以二者在设计思想是相通的。

  • VfsResource

VfsResource 对 JBoss Virtual File System (VFS) 提供了支持,针对 JBoss VFS 的说明,官网简介如下:

The Virtual File System (VFS) framework is an abstraction layer designed to simplify the programmatic access to file system resources. One of the key benefits of VFS is to hide certain file system details and allow for file system layouts that are not required to reflect a real file system. This allows for great flexibility and makes it possible to navigate arbitrary structures (ex. archives) as though they are part of a single file system.

具体没用过,不多做解释。

  • InputStreamResource

InputStreamResource 基于给定的 InputStream 来创建资源,流是一般文件的更低一层,程序设计的共性就是越往底层走需要考虑的问题就越多,所以 Spring 明确表示,如果有相应的上层实现则不推荐直接使用 InputStreamResource。

资源加载

Spring 定义了 ResourceLoader 接口用于抽象对于资源的加载操作,该接口的定义如下:

1
2
3
4
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}

其中 ResourceLoader#getResource 方法用于获取指定路径的 Resource 对象;方法 ResourceLoader#getClassLoader 则返回当前 ResourceLoader 所使用的类加载器,一些情况下我们可能需要基于该类加载器执行一些相对定位操作。

image

上述 UML 图展示了 ResourceLoader 的继承关系,我们可以将所有的接口分为加载器和解析器两类。加载器的作用不言而喻,对于解析器而言,由前面的分析我们知道 Spring 针对不同资源类型分别定义响应的 Resource 实现类,Spring 通过解析器解析具体资源类型,并加载返回对应的 Resource 对象。

在日常使用过程中,我们通常都是以 Ant 风格来配置资源路径。Ant 风格的支持给我们的配置带来了极大的灵活性,这也是 PathMatchingResourcePatternResolver 的功劳。路径的解析本质上依赖于各种规则,Ant 风格也不例外,有兴趣的同学可以自己阅读一下 PathMatchingResourcePatternResolver 解析路径的过程。

Bean 的解析与注册

image

当启动 IoC 容器时,Spring 需要读取 bean 相关的配置,并将各个 bean 的配置封装成 BeanDefinition 对象注册到容器中,上图展示了这一解析并注册过程的交互时序。当我们执行 new XmlBeanFactory(resource) 的时候已经完成了将配置文件包装成 Spring 定义的 Resource 对象,并开始执行解析和注册过程。XmlBeanFactory 的构造方法定义如下:

1
2
3
4
5
6
7
8
9
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 加载 XML 资源
this.reader.loadBeanDefinitions(resource);
}

构造方法首先是调用了父类 DefaultListableBeanFactory 构造方法,这是一个非常核心的类,它包含了简单 IoC 容器所具备的重要功能,是一个 IoC 容器的基本实现。然后调用了 XmlBeanDefinitionReader#loadBeanDefinitions 方法开始加载配置。

Spring 在设计上采用了许多程序设计的基本原则,比如迪米特法则、开闭原则,以及接口隔离原则等等,这样的设计为后续的扩展提供了极大的灵活性,也增强了模块的复用性。

Spring 使用了专门的 BeanDefinition 加载器对资源进行加载,这里使用的是 XmlBeanDefinitionReader 类,用来加载基于 XML 文件配置的 bean。整个加载过程可以概括如下:

  1. 利用 EncodedResource 二次包装 Resource 对象;
  2. 获取资源对应的输入流,并构造 InputSource 对象;
  3. 获取 XML 文件的实体解析器和验证模式,并加载 XML 文件返回 Document 对象;
  4. 由 Document 对象解析并注册 BeanDefinition。

上述过程执行期间,Spring 会暂存正在加载的 Resource 对象,避免在配置中出现配置文件之间的循环 import。

下面针对上述步骤展开说明。首先来看 步骤一 ,这一步会采用 EncodedResource 对 Resource 对象进行二次封装。EncodedResource 从命名来看是对于 Resource 的一种修饰,而不是用来描述某一类具体的资源,所以 EncodedResource 并没有实现 Resource 接口,而是采用了类似装饰者模式的方式对 Resource 对象进行包装,以实现对 Resource 输入流按照指定的字符集进行编码。

完成了对 Resource 对象进行编码封装之后, 步骤二 会依据编码将 Resource 对应的输入流封装成 InputSource 对象,从而为加载 XML 做准备。InputSource 并非是 Spring 中定义的类,这个类是 JDK 提供的对 XML 实体的原生支持

接下来,Spring 会调用 XmlBeanDefinitionReader#doLoadBeanDefinitions 方法正式开始针对 BeanDefinition 的加载和注册过程,对应 步骤三步骤四 ,该方法实现如下:

1
2
3
4
5
6
7
8
9
10
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 获取 XML 文件的实体解析器和验证模式,并加载 XML 文件返回 Document 对象
Document doc = this.doLoadDocument(inputSource, resource);
// 由 Document 对象解析并注册 BeanDefinition
int count = this.registerBeanDefinitions(doc, resource);
return count;
}
// ... 省略异常处理
}

方法逻辑还是很清晰的,第一步加载 XML 获取 Document 对象,第二步由 Document 对象解析得到 BeanDefinition 对象并注册到 IoC 容器中。

加载 XML 文件首先会获取对应的实体解析器和验证模式,方法 XmlBeanDefinitionReader#doLoadDocument 实现了获取实体解析器、验证模式,以及构造 Document 对象的逻辑:

1
2
3
4
5
6
7
8
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(
inputSource,
this.getEntityResolver(), // 获取实体解析器
this.errorHandler,
this.getValidationModeForResource(resource), // 获取验证模式
this.isNamespaceAware());
}

XML 是半结构化数据,其验证模式用于保证结构的正确性,常见的验证模式有 DTD 和 XSD 两种。获取验证模式的过程实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
// 手动指定了验证模式
return validationModeToUse;
}

// 没有指定验证模式,自动检测
int detectedMode = this.detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}

// 检测验证模式失败,默认采用 XSD 模式
return VALIDATION_XSD;
}

上述实现描述了获取验证模式的执行流程,如果没有手动指定那么 Spring 会去自动检测。对于 XML 文件的解析,SAX 首先会读取 XML 文件头声明,以获取相应验证文件地址,并下载验证文件。网络异常会影响下载过程,这个时候可以通过注册一个实体解析器实现寻找验证文件的逻辑。

完成了对于验证模式和解析器的获取,就可以开始加载 Document 对象了,这里本质上调用的是 DefaultDocumentLoader#loadDocument 方法,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Document loadDocument(InputSource inputSource,
EntityResolver entityResolver,
ErrorHandler errorHandler,
int validationMode,
boolean namespaceAware) throws Exception {

DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}

整个过程与我们平常解析 XML 文件的流程大致相同。

完成了对 XML 文件到 Document 对象的构造,我们终于可以解析 Document 对象并注册 BeanDefinition 了,这一过程由 XmlBeanDefinitionReader#registerBeanDefinitions 方法实现:

1
2
3
4
5
6
7
8
9
10
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用 DefaultBeanDefinitionDocumentReader 构造
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
// 记录之前已经注册的 BeanDefinition 数目
int countBefore = this.getRegistry().getBeanDefinitionCount();
// 加载并注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
// 返回本次加载的 BeanDefinition 数目
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

上述方法所做的工作就是创建对应的 BeanDefinitionDocumentReader 对象,基于该对象加载并注册 BeanDefinition,并最终返回本次新注册的 BeanDefinition 的数量。加载并注册 BeanDefinition 的过程具体由 DefaultBeanDefinitionDocumentReader 实现:

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
35
36
37
38
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 从文档的 ROOT 结点开始解析
this.doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {
// 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.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);

// 处理 profile 标签(其作用类比 pom.xml 中的 profile)
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = 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 (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}

// 模板方法,预处理
this.preProcessXml(root);
// 解析并注册 BeanDefinition
this.parseBeanDefinitions(root, this.delegate);
// 模板方法,后处理
this.postProcessXml(root);

this.delegate = parent;
}

解析的过程首先处理 <profile/> 标签,这个属性在 Spring 中不是很常用,不过在 maven 中倒是挺常见,可以类比进行理解,即在配置多套环境时可以根据部署的具体环境来选择使用哪一套配置。上述方法会先检测是否配置了 profile 标签,如果是就需要从上下文环境中确认当前激活了哪一套配置。

具体解析并注册 BeanDefinition 的过程交由 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 方法完成,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 解析默认标签
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 解析默认标签
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
}
// 解析自定义标签
else {
delegate.parseCustomElement(ele);
}
}
}
}
// 解析自定义标签
else {
delegate.parseCustomElement(root);
}
}

解析期间会判断当前标签是默认标签还是自定义标签,并按照不同的策略进行解析,这是一个复杂的过程,后面会用文章进行针对性讲解,这里暂不深究。

到这里我们已经完成了由静态配置到 BeanDefinition 的解析,并注册到 IoC 容器中的过程,下一节将继续探究如何创建并初始化 bean 实例。

Bean 实例的创建和初始化

完成了对 bean 配置的加载和解析之后,相应的配置就全部转换成 BeanDefinition 对象的形式存在于 IoC 容器中。接下来我们可以调用 AbstractBeanFactory#getBean 方法获取 bean 实例,该方法实现如下:

1
2
3
public Object getBean(String name) throws BeansException {
return this.doGetBean(name, null, null, false);
}

上述方法只是简单的将请求委托给 AbstractBeanFactory#doGetBean 方法进行处理,这也符合我们的预期。方法 AbstractBeanFactory#doGetBean 可以看作是是获取 bean 实例的整体框架代码,通过调度各个模块完成对 bean 实例及其依赖的 bean 实例的初始化操作,并最终返回我们期望的 bean 实例。方法实现如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
protected <T> T doGetBean(final String name,
@Nullable final Class<T> requiredType,
@Nullable final Object[] args,
boolean typeCheckOnly) throws BeansException {

/*
* 获取 name 对应的真正 beanName
*
* 因为传入的参数可以是 alias,也可能是 FactoryBean 的 name,所以需要进行解析,包含以下内容:
* 1. 如果是 FactoryBean,则去掉 “&” 前缀
* 2. 沿着引用链获取 alias 对应的最终 name
*/
final String beanName = this.transformedBeanName(name);
Object bean;

/*
* 尝试从单例集合中获取对应的单实例,
* 在实例化 bean 的时候可能需要实例化依赖的 bean 对象,Spring 为了避免循环依赖会采用早期引用机制
*/
Object sharedInstance = this.getSingleton(beanName);
// 目标实例已经实例化过
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
} else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 处理 FactoryBean
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// 目标实例不存在
else {
// Fail if we're already creating this bean instance: We're assumably within a circular reference.
if (this.isPrototypeCurrentlyInCreation(beanName)) {
/*
* 只有在单例模式下才会尝试解决循环依赖问题,
* 对于原型模式,如果存在循环依赖,直接抛出异常
*/
throw new BeanCurrentlyInCreationException(beanName);
}

// 获取父 BeanFactory 实例
BeanFactory parentBeanFactory = this.getParentBeanFactory();
// 如果已经加载的 bean 定义中不包含目标 bean,则尝试从父 BeanFactory 中获取
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
// 递归到父 BeanFactory 中进行检索
String nameToLookup = this.originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory)
.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
} else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
} else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}

// 如果不仅仅是做类型检查,则标记该 bean 即将被创建
if (!typeCheckOnly) {
this.markBeanAsCreated(beanName);
}

try {
// 如果存在父 bean,则继承父 bean 定义
final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
// 检查 bean 是否是抽象的,如果是则抛出异常
this.checkMergedBeanDefinition(mbd, beanName, args);

// 加载当前 bean 依赖的 bean 实例
String[] dependsOn = mbd.getDependsOn();
// 存在依赖,递归实例化依赖的 bean 实例
if (dependsOn != null) {
for (String dep : dependsOn) {
// 检查是否存在循环依赖
if (this.isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 缓存依赖调用
this.registerDependentBean(dep, beanName);
try {
// 初始化依赖的 bean 实例
this.getBean(dep);
} catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}

/* 创建 bean 实例 */

// scope == singleton
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
// 实例化 bean 对象
return this.createBean(beanName, mbd, args);
} catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
this.destroySingleton(beanName); // 清理工作,从单例缓存中移除
throw ex;
}
});
// 处理 FactoryBean
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// scope == prototype
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
// 设置正在创建的状态
this.beforePrototypeCreation(beanName);
// 创建 bean 实例
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
// 处理 FactoryBean
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// other scope
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
this.beforePrototypeCreation(beanName);
try {
return this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
});
// 处理 FactoryBean
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);
}
}
} catch (BeansException ex) {
this.cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}

// 如果要求做类型检查,则检查 bean 的实际类型是否是期望的类型,对应 getBean 时指定的 requireType
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
// 执行类型转换,转换成期望的类型
T convertedBean = this.getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
} catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}

整个方法的执行流程可以概括为:

  1. 获取参数 name 对应的真正的 beanName;
  2. 检查缓存或者实例工厂中是否有对应的单例,若存在则进行实例化并返回对象,否则继续往下执行;
  3. 执行 prototype 类型依赖检查,防止循环依赖;
  4. 如果当前 BeanFactory 中不存在需要的 bean 实例,则尝试从父 BeanFactory 中获取;
  5. 将之前解析过程返得到的 GenericBeanDefinition 对象合并为 RootBeanDefinition 对象,便于后续处理;
  6. 如果存在依赖的 bean,则递归初始化依赖的 bean 实例;
  7. 依据当前 bean 的作用域对 bean 进行实例化;
  8. 如果对返回 bean 类型有要求则进行检查,按需做类型转换;
  9. 返回 bean 实例。

上述方法从整体来看就是一个框架代码,总结了从接收一个 beanName 到返回对应 bean 实例的完整流程。

总结

本文从整体的角度分析了一个 bean 从 XML 配置,到载入 IoC 容器中封装成 BeanDefinition 对象,最后依据请求初始化并返回 bean 实例的完整流程,目的在于从整体建立对 IoC 容器运行机制的认识。从下一篇开始,我们将回到起点,沿着本文梳理的 IoC 容器运行主线,对中间执行的具体细节进行深入分析。

参考

  1. Spring 源码深度解析