死磕Spring之IoC篇 - BeanDefinition 的加载阶段(XML 文件)
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读
Spring 版本:5.1.14.RELEASE
开始阅读这一系列文章之前,建议先查看《深入了解 Spring IoC(面试题)》这一篇文章
该系列其他文章请查看:《死磕 Spring 之 IoC 篇 - 文章导读》
BeanDefinition 的加载阶段(XML 文件)
上一篇文章 《Bean 的“前身”》 对 BeanDefinition 进行了介绍,Bean 是根据 BeanDefinition 配置元信息对象生成的。我们在 Spring 中通常以这两种方式定义一个 Bean:面向资源(XML、Properties)、面向注解,那么 Spring 是如何将这两种方式定义的信息转换成 BeanDefinition 对象的,接下来会先分析面向资源(XML、Properties)这种方式 Spring 是如何处理的
下来熟悉一段代码:
dependency-lookup-context.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:component-scan base-package="org.geekbang.thinking.in.spring.ioc.overview" /> -->
<bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
<property name="id" value="1"/>
<property name="name" value="小马哥"/>
</bean>
</beans>
// 创建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// XML 配置文件 ClassPath 路径
String location = "classpath:/META-INF/dependency-lookup-context.xml";
// 加载配置
int beanDefinitionsCount = reader.loadBeanDefinitions(location);
System.out.println("Bean 定义加载的数量:" + beanDefinitionsCount);
// 依赖查找
System.out.println(beanFactory.getBean("user"));;
这段代码是 Spring 中编程式使用 IoC 容器,我们可以看到 IoC 容器的使用过程大致如下:
- 创建 BeanFactory 对象(底层 IoC 容器)
- 创建 BeanDefinitionReader 对象(资源解析器),关联第
1
步创建的 BeanFactory - 通过 BeanDefinitionReader 加载 XML 配置文件资源,解析出所有的 BeanDefinition 对象
- 进行依赖查找
上面的第 3
步会解析 Resource 资源,将 XML 文件中定义的 Bean 解析成 BeanDefinition 配置元信息对象,并往 BeanDefinitionRegistry 注册中心注册,此时并没有生成对应的 Bean 对象,需要通过依赖查找获取到 Bean。当然,我们在实际场景中一般不会这样使用 Spring,这些工作都会有 Spring 来完成。接下来我们一起来看看 Sping 是如何加载 XML 文件的
BeanDefinitionReader 体系结构
org.springframework.beans.factory.support.BeanDefinitionReader
接口的类图如下所示:
总览:
-
org.springframework.beans.factory.support.BeanDefinitionReader
接口,BeanDefinition 读取器 -
org.springframework.beans.factory.support.AbstractBeanDefinitionReader
抽象类,提供通用的实现,具体的资源加载逻辑在由子类实现 -
org.springframework.beans.factory.xml.XmlBeanDefinitionReader
,XML 文件资源解析器,解析出 BeanDefinition 配置元信息对象并注册 -
org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
,Properties 文件资源解析器
BeanDefinitionReader 接口
org.springframework.beans.factory.support.BeanDefinitionReader
接口,BeanDefinition 读取器,定义了加载资源的方法,代码如下:
public interface BeanDefinitionReader {
/** 返回 BeanDefinition 注册中心 */
BeanDefinitionRegistry getRegistry();
/** 返回 Resource 资源加载器,默认为 PathMatchingResourcePatternResolver */
@Nullable
ResourceLoader getResourceLoader();
/** 返回类加载器 */
@Nullable
ClassLoader getBeanClassLoader();
/** 返回 Bean 的名称生成器,默认为 DefaultBeanNameGenerator */
BeanNameGenerator getBeanNameGenerator();
/** 从 Resource 资源中加载 BeanDefinition 并返回数量 */
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
AbstractBeanDefinitionReader 抽象类
org.springframework.beans.factory.support.AbstractBeanDefinitionReader
抽象类,实现了 BeanDefinitionReader 和 EnvironmentCapable 接口,代码如下:
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {
private final BeanDefinitionRegistry registry;
@Nullable
private ResourceLoader resourceLoader;
@Nullable
private ClassLoader beanClassLoader;
private Environment environment;
private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
count += loadBeanDefinitions(resource);
}
return count;
}
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
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);
if (actualResources != null) {
// 添加到 actualResources 中
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);
if (actualResources != null) {
// 添加到 actualResources 中
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
// ... 省略相关代码
}
在实现的方法中,最终都会调用 int loadBeanDefinitions(Resource resource)
这个方法,该方法在子类中实现
XmlBeanDefinitionReader
org.springframework.beans.factory.xml.XmlBeanDefinitionReader
,XML 文件资源解析器,解析出 BeanDefinition 配置元信息对象并注册
构造函数
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
* 禁用验证模式
*/
public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;
/**
* 自动获取验证模式
*/
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
/**
* DTD 验证模式
*/
public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;
/**
* XSD 验证模式
*/
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
/** Constants instance for this class. */
private static final Constants constants = new Constants(XmlBeanDefinitionReader.class);
/**
* 验证模式,默认为自动模式。
*/
private int validationMode = VALIDATION_AUTO;
private boolean namespaceAware = false;
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
/**
* 解析过程中异常处理器
*/
private ProblemReporter problemReporter = new FailFastProblemReporter();
private ReaderEventListener eventListener = new EmptyReaderEventListener();
private SourceExtractor sourceExtractor = new NullSourceExtractor();
@Nullable
private NamespaceHandlerResolver namespaceHandlerResolver;
private DocumentLoader documentLoader = new DefaultDocumentLoader();
@Nullable
private EntityResolver entityResolver;
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
/**
* XML 验证模式探测器
*/
private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();
/**
* 当前线程,正在加载的 EncodedResource 集合。
*/
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>(
"XML bean definition resources currently being loaded");
/**
* Create new XmlBeanDefinitionReader for the given bean factory.
* @param registry the BeanFactory to load bean definitions into,
* in the form of a BeanDefinitionRegistry
*/
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
}
loadBeanDefinitions 方法
loadBeanDefinitions(Resource resource)
方法,解析 Resource 资源的入口,方法如下:
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
// <1> 获取当前线程正在加载的 Resource 资源集合,添加当前 Resource,防止重复加载
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) { // 将当前资源加入记录中。如果已存在,抛出异常,防止循环加载同一资源出现死循环
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// <2> 从 Resource 资源获取 InputStream 流对象(支持编码)
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// <3> 【核心】执行加载 Resource 资源过程,解析出 BeanDefinition 进行注册
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
// 关闭流
inputStream.close();
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
// <4> 从当前线程移除当前加载的 Resource 对象
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
将 Resource 封装成 EncodedResource 对象,目的是让资源对象可设置编码
- 获取当前线程正在加载的 Resource 资源集合,添加当前 Resource,防止重复加载
- 从 Resource 资源获取 InputStream 流对象(支持编码)
- 【核心】调用
doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,执行加载 Resource 资源过程,解析出 BeanDefinition 进行注册 - 从当前线程移除当前加载的 Resource 对象
doLoadBeanDefinitions 方法
doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,执行加载 Resource 资源过程,解析出 BeanDefinition 进行注册,方法如下:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// <1> 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// <2> 根据 Document 实例,解析出 BeanDefinition 们并注册,返回注册数量
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
// 省略 catch 各种异常
}
- 调用
doLoadDocument(InputSource inputSource, Resource resource)
方法,获取 XML Document 实例 - 调用
registerBeanDefinitions(Document doc, Resource resource)
方法,根据 Document 实例,解析出 BeanDefinition 们并注册,返回注册数量
doLoadDocument 方法
doLoadDocument(InputSource inputSource, Resource resource)
方法,获取 Resource 资源对应的 XML Document 实例,方法如下:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// <3> 通过 DefaultDocumentLoader 根据 Resource 获取一个 Document 对象
return this.documentLoader.loadDocument(inputSource,
getEntityResolver(), // <1> 获取 `org.xml.sax.EntityResolver` 实体解析器,ResourceEntityResolver
this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware()); // <2> 获取 XML 文件验证模式,保证 XML 文件的正确性
}
- 获取
org.xml.sax.EntityResolver
实体解析器,ResourceEntityResolver,根据 publicId 和 systemId 获取对应的 DTD 或 XSD 文件,用于对 XML 文件进行验证,这个类比较关键,在后续文章会讲到 - 获取 XML 文件验证模式,保证 XML 文件的正确性,通常情况下都是 XSD 模式
- 获取指定的验证模式,如果手动指定,则直接返回,通常情况下不会
- 从 Resource 资源中获取验证模式,根据 XML 文件的内容进行获取,如果包含
DOCTYPE
内容则为 DTD 模式,否则为 XSD 模式 - 如果还没有获取到验证模式,则默认为 XSD 模式
- 通过 DefaultDocumentLoader 根据 Resource 获取一个 Document 对象
- 创建 DocumentBuilderFactory 对象
factory
,开启校验 - 根据
factory
创建 DocumentBuilder 对象builder
,设置 EntityResolver(第1
步创建的)、ErrorHandler 属性 - 通过
builder
对inputSource
(Resource 资源)进行解析,返回一个 Document 对象
- 创建 DocumentBuilderFactory 对象
上述过程目的就是获取到 Resource 资源对应的 Document 对象,需要经过校验和解析两个过程
registerBeanDefinitions 方法
registerBeanDefinitions(Document doc, Resource resource)
方法,根据 Document 实例,解析出 BeanDefinition 们并注册,返回注册数量,方法如下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// <1> 创建 BeanDefinitionDocumentReader 对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// <2> 获取已注册的 BeanDefinition 数量
int countBefore = getRegistry().getBeanDefinitionCount();
// <3> 创建 XmlReaderContext 对象(读取 Resource 资源的上下文对象)
// <4> 根据 Document、XmlReaderContext 解析出所有的 BeanDefinition 并注册
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// <5> 计算新注册的 BeanDefinition 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
- 创建 DefaultBeanDefinitionDocumentReader 对象
documentReader
- 获取已注册的 BeanDefinition 数量
- 创建 XmlReaderContext 对象(读取 Resource 资源的上下文对象),注意这里会初始化一个 DefaultNamespaceHandlerResolver 对象,用于处理自定义标签(XML 文件),比较关键,在后续文章会讲到
- 根据 Document、XmlReaderContext 解析出所有的 BeanDefinition 并注册,调用
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
方法 - 计算新注册的 BeanDefinition 数量并返回
拓展:DTD 与 XSD 的区别?
DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。其实 DTD 就相当于 XML 中的 “词汇”和“语法”,我们可以通过比较 XML 文件和 DTD 文件 来看文档是否符合规范,元素和标签使用是否正确。
DTD 在一定的阶段推动了 XML 的发展,但是它本身存在着一些缺陷:
- 它没有使用 XML 格式,而是自己定义了一套格式,相对解析器的重用性较差;而且 DTD 的构建和访问没有标准的编程接口,导致解析器很难简单的解析 DTD 文档
- DTD 对元素的类型限制较少;同时其他的约束力也比较弱
- DTD 扩展能力较差
- 基于正则表达式的 DTD 文档的描述能力有限
XSD(XML Schemas Definition),即 XML Schema 语言,针对 DTD 的缺陷由 W3C 在 2001 年推出。XML Schema 本身就是一个 XML 文档,使用的是 XML 语法,因此可以很方便的解析 XSD 文档。相对于 DTD,XSD 具有如下优势:
- XML Schema 基于 XML,没有专门的语法
- XML Schema 可以像其他 XML 文件一样解析和处理
- XML Schema 比 DTD 提供了更丰富的数据类型
- XML Schema 提供可扩充的数据模型
- XML Schema 支持综合命名空间
- XML Schema 支持属性组
总结
我们在 Spring 中通常以这两种方式定义一个 Bean:面向资源(XML、Properties)、面向注解,对于第一种方式如果定义的是一个 XML 文件,Spring 会通过 XmlBeanDefinitionReader 加载该 XML 文件,获取该 Resource 资源的 org.w3c.dom.Document
对象,这个过程会经过校验、解析两个步骤