死磕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 容器的使用过程大致如下:

  1. 创建 BeanFactory 对象(底层 IoC 容器)
  2. 创建 BeanDefinitionReader 对象(资源解析器),关联第 1 步创建的 BeanFactory
  3. 通过 BeanDefinitionReader 加载 XML 配置文件资源,解析出所有的 BeanDefinition 对象
  4. 进行依赖查找

上面的第 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 对象,目的是让资源对象可设置编码

  1. 获取当前线程正在加载的 Resource 资源集合,添加当前 Resource,防止重复加载
  2. 从 Resource 资源获取 InputStream 流对象(支持编码)
  3. 【核心】调用 doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行加载 Resource 资源过程,解析出 BeanDefinition 进行注册
  4. 从当前线程移除当前加载的 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 各种异常
}
  1. 调用 doLoadDocument(InputSource inputSource, Resource resource) 方法,获取 XML Document 实例
  2. 调用 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 文件的正确性
}
  1. 获取 org.xml.sax.EntityResolver 实体解析器,ResourceEntityResolver,根据 publicId 和 systemId 获取对应的 DTD 或 XSD 文件,用于对 XML 文件进行验证,这个类比较关键,在后续文章会讲到
  2. 获取 XML 文件验证模式,保证 XML 文件的正确性,通常情况下都是 XSD 模式
    1. 获取指定的验证模式,如果手动指定,则直接返回,通常情况下不会
    2. 从 Resource 资源中获取验证模式,根据 XML 文件的内容进行获取,如果包含 DOCTYPE 内容则为 DTD 模式,否则为 XSD 模式
    3. 如果还没有获取到验证模式,则默认为 XSD 模式
  3. 通过 DefaultDocumentLoader 根据 Resource 获取一个 Document 对象
    1. 创建 DocumentBuilderFactory 对象 factory,开启校验
    2. 根据 factory 创建 DocumentBuilder 对象 builder,设置 EntityResolver(第 1 步创建的)、ErrorHandler 属性
    3. 通过 builderinputSource(Resource 资源)进行解析,返回一个 Document 对象

上述过程目的就是获取到 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;
}
  1. 创建 DefaultBeanDefinitionDocumentReader 对象 documentReader
  2. 获取已注册的 BeanDefinition 数量
  3. 创建 XmlReaderContext 对象(读取 Resource 资源的上下文对象),注意这里会初始化一个 DefaultNamespaceHandlerResolver 对象,用于处理自定义标签(XML 文件),比较关键,在后续文章会讲到
  4. 根据 Document、XmlReaderContext 解析出所有的 BeanDefinition 并注册,调用 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法
  5. 计算新注册的 BeanDefinition 数量并返回

拓展:DTD 与 XSD 的区别?

DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。其实 DTD 就相当于 XML 中的 “词汇”和“语法”,我们可以通过比较 XML 文件和 DTD 文件 来看文档是否符合规范,元素和标签使用是否正确。

DTD 在一定的阶段推动了 XML 的发展,但是它本身存在着一些缺陷

  1. 它没有使用 XML 格式,而是自己定义了一套格式,相对解析器的重用性较差;而且 DTD 的构建和访问没有标准的编程接口,导致解析器很难简单的解析 DTD 文档
  2. DTD 对元素的类型限制较少;同时其他的约束力也比较弱
  3. DTD 扩展能力较差
  4. 基于正则表达式的 DTD 文档的描述能力有限

XSD(XML Schemas Definition),即 XML Schema 语言,针对 DTD 的缺陷由 W3C 在 2001 年推出。XML Schema 本身就是一个 XML 文档,使用的是 XML 语法,因此可以很方便的解析 XSD 文档。相对于 DTD,XSD 具有如下优势

  1. XML Schema 基于 XML,没有专门的语法
  2. XML Schema 可以像其他 XML 文件一样解析和处理
  3. XML Schema 比 DTD 提供了更丰富的数据类型
  4. XML Schema 提供可扩充的数据模型
  5. XML Schema 支持综合命名空间
  6. XML Schema 支持属性组

总结

我们在 Spring 中通常以这两种方式定义一个 Bean:面向资源(XML、Properties)面向注解,对于第一种方式如果定义的是一个 XML 文件,Spring 会通过 XmlBeanDefinitionReader 加载该 XML 文件,获取该 Resource 资源的 org.w3c.dom.Document 对象,这个过程会经过校验、解析两个步骤

相关推荐

发表评论

路人甲

网友评论(0)