2 容器的基本实现
2 容器的基本实现
2.1 容器的基本用法
容器的基本用法见代码 com.stu.spring.context.chapter02.BeanFactoryTest
2.2 功能分析
对应上述代码,容器的加载步骤大致如下:
- 读取配置文件
chapter02\myContainerTest.xml
。对应ConfigReader
类,用于读取及验证配置文件。 - 根据
myContainerTest.xml
中的配置找到对应的类的配置,并实例化。对应ReflectionUtil
类,用于根据配置文件中的配置进行反射实例化。 - 调用实例化后的实例。
2.3 工程搭建
没有用源码环境,忽略。
2.4.1 Spring 的结构组成
即项目源码目录结构。
2.4.2 核心类介绍
了解 Spring 中核心的两个类 DefaultListableBeanFactory
和 XmlBeanDefinitionReader
。
DefaultListableBeanFactory
XmlBeanFactory
继承自 DefaultListableBeanFactory
,而 DefaultListableBeanFactory
是整个 bean 加载的核心部分,是 Spring 注册及加载 bean 的默认实现。
可以在 idea 中使用 Ctrl + H,然后将菜单切为 Supertypes Hierarchy 即可查看 DefaultListableBeanFactory
的层次结构图。以下是其类图:
- AliasRegistry:定义了对 Alias 简单的增删改操作。
- BeanFactory:获取 bean 以及 bean 的 isSingleton, isPrototype, isTypeMatch 等属性。
- SingletonBeanRegistry:定义对单例的注册获取。
- SimpleAliasRegistry:使用了 Map 作为 Alias 的缓存,并实现 AliasRegistry 接口。
- BeanDefinitionRegistry:继承 AliasRegitry,定义了对 BeanDefinitionRegistry 的基本操作。
- ListableBeanFactory:获取 Bean 的配置清单。
- HierarchicalBeanFactory:继承 BeanFactory, 在此基础增加了对 ParentBeanFactroy 的支持,多出 BeanFactory getParentBeanFactory(); 和 boolean containsLocalBean(String name); 方法。
- DefaultSingletonBeanRegistry:对 SingletonBeanRegistry 的实现。
- ConfigurableBeanFactory:提供配置 Factory 的各种方法。
- FactoryBeanRegistrySupport:继承 DefaultSingletonBeanRegistry,并在此基础上增加对 FactoryBean 的一些操作。
- AutowireCapableBeanFactory:提供创建 bean,自动注入,初始化以及应用 bean 的后处理器。
- AbstractBeanFactory:综合 FactoryBeanRegitrySupport 和 ConfigurableBeanFactory 的功能。
- ConfigurableListableBeanFactory: BeanFactory 配置清单,指定忽略类型及接口等。
- AbstractAutowireCapableBeanFactory:结合 AbstractBeanFactory 和 接口 AutowireCapableBeanFactory。
- DefaultListableBeanFactory:综合上述所有功能,主要是对 bean 注册后的处理。
XmlBeanDefinitionReader
XML 配置文件的读取是 Spring 中重要的功能,因为 Spring 的大部分功能都是以配置作为切入点。以下是相关类介绍:
- ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的 Resource;
- BeanDefinitionReader:主要定义资源文件的读取并转换为 BeanDefinition 的各个功能;
- EnvironmentCapable:定义获取 Environment 方法;
- DocumentLoader:定义从资源文件加载到转换为 Document 的功能;
- AbstractBeanDefinitionReader:对 EnvironmentCapable,BeanDefinitionReader 类定义的功能进行实现;
- BeanDefinitionDocumentReader:定义读取 Document 并注册 BeanDefinition 的功能;
- BeanDefinitionParserDelegate:定义解析 Element 的各种方法。
以下是其类图:
在 XmlBeanDifinitionReader
中主要包含以下几步的处理:
- 通过继承自
AbstractBeanDefinitionReader
中的方法,来使用ResourLoader
将资源文件路径转换对应的Resource
文件; - 通过
DocumentLoader
对Resource
文件进行转换,将Resource
文件转换为Document
文件; - 通过实现
BeanDefinitionDocumentReader
的DefaultBeanDefinitionDocumentReader
类对Docment
进行解析,并使用BeanDefinitionPareserDelegate
对Element
进行解析。
2.5 容器的基础 XmlBeanFactory
代码入口如下,我们从这个入口开始跟踪源码。
package com.stu.spring.context.chapter02;
//...
new XmlBeanFactory(new ClassPathResource("chapter02\\myContainerTest.xml"));
//...
以下是 XmlBeanFactory
初始化时序图。
2.5.1 配置文件封装
ClassPathResource 作用
Java 的 URL 的实现机制中,URL 没有提供一些基本的方法,如检查当前资源是否存在,检查当前资源是否可读等方法。因而 Spring 对其内部使用到的资源实现了自己的抽象结构:org.springframework.core.io.Resource
接口来封装底层资源。详细代码略。
Spring 对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource
),Classpath 资源(ClasspathResource
),URL 资源(URLResource
),InputStream 资源(InputStreamResource
),Byte 数组(ByteArrayResource
)等。
日常的开发工作中,资源的加载,我们可以使用如 new ClassPathResource("chapter02\\myContainerTest.xml").getInputStream()
等进行实现。这部分只是 Spring 基于 java 流做了一些包装。
loadBeanDefinitions 入口
我们从 new (XmlBeanFactory)
跟踪找到 loadBeanDefinitions
方法,其是我们分析的重点之一,该方法是资源加载的真正实现。
public class XmlBeanFactory extends DefaultListableBeanFactory {
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
// 1 加载数据前,还有一个调用父类构造函数初始化的过程,主要是忽略给定接口的自动装配功能。详见如下代码及备注
super(parentBeanFactory);
// 2 加载数据,见下个小节分析
this.reader.loadBeanDefinitions(resource);
}
}
public abstract class AbstractAutowireCapableBeanFactory{
// 忽略给定接口的自动装配功能说明
public AbstractAutowireCapableBeanFactory() {
super();
/**
* Spring采用的是懒加载方式,当类 A 中有属性 B 时,在从容器中获取 A 对象时会查看属性是否初始化,没有的话会自动初始化 B。
* 然而有些情况下是不希望初始化属性 B 的。例如 B 实现了 BeanNameAware、BeanFactoryAware、
* BeanClassLoaderAware 接口(该接口作用是获取其在容器中的名称、容器、类加载器)。
*/
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
}
2.5.2 加载 Bean
详见示例代码,主要是以下几个步骤。
- 封装资源文件,以及处理可能的编码问题
new EncodedResource(resource)
;
- 封装资源文件,以及处理可能的编码问题
- 获取输入流;
- 根据前面参数,继续调用
doLoadBeanDefinitions
,这块是核心逻辑
- 3.1 获取对 XML 文件的验证模式。
- 3.2 加载 XML 文件,并得到对应的 Document。
- 3.3 根据返回的 Document 注册 Bean 信息。
- 根据前面参数,继续调用
封装资源文件代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 1. 封装资源文件,以及处理可能的编码问题 new EncodedResource(resource):
return loadBeanDefinitions(new EncodedResource(resource));
}
}
public class EncodedResource implements InputStreamSource {
// 设置编码属性,后续 Spring 会使用相应的编码作为输入流的编码
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
}
}
获取输入流、doLoadBeanDefinitions
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 2 获取输入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
// org.xml.sax.InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 3 根据前面参数,继续调用 doLoadBeanDefinitions,这块是核心逻辑
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
// 3.1 获取对 XML 文件的验证模式。
// 3.2 加载 XML 文件,并得到对应的 Document。
Document doc = doLoadDocument(inputSource, resource);
// 3.3 根据返回的 Document 注册 Bean 信息。
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
}
其中核心逻辑的 3 个步骤详细描述如下。
2.6 获取 XML 的验证模式
2.6.1 DTD 与 XSD 区别
略。
2.6.2 验证模式的读取
验证模式的读取代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 3.1 获取对 XML 文件的验证模式。
// 3.2 加载 XML 文件,并得到对应的 Document。
// loadDocument 委托给了 DocumentLoader 去执行,真正调用的是其子类,DefaultDocumentLoader。注意 getEntityResolver() 方法
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
// 3.1 获取对 XML 文件的验证模式。
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
// 1 如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 2 如果未指定则使用自动检测,将自动检测验证模式的工作委托给了专门处理类 XmlValidationModeDetector
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
}
2.7 获取 Document
loadDocument 代码
public class DefaultDocumentLoader implements DocumentLoader {
// 3.2 加载 XML 文件,并得到对应的 Document。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
}
整个 SAX 解析 XML 文档的套路都是差不多的,这里也不展开讨论,主要是注意下 EntityResolver
参数。
2.7.1 EntityResolver 用法
EntityResolver 用法
简单来说与自定义标签和默认标签的解析验证有关,比如联网下载 xsd 会很慢,因此内置了一套处理逻辑,当然也可以自定义处理逻辑。
具体不展开了,可以参考 EntityResolver 作用,以下是 XSD 格式的 Spring 配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
Spring 中使用 DelegatingEntityResolver
类为 EntityResolver 的实现类,resolveEntity 示例代码见 DelegatingEntityResolver.resolveEntity
。
2.8 解析并注册 BeanDefinitions
解析并注册 BeanDefinitions 代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 在实例化 BeanDefinitionReader 时候会将 BeanDefinitionRegistry 传入,默认使用继承自 DefaultListableBeanFactory 的子类
// 记录统计前 BeanDefinition 的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 重点:加载及注册 bean 的核心处理逻辑
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的 BeanDefinition 个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
}
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
protected void doRegisterBeanDefinitions(Element root) {
// 专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理 profile 属性,注意:没有该 profile 时,是直接 return 掉的
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 解析前处理,留给子类实现
preProcessXml(root);
// 重点:解析并注册 BeanDefinition 的核心处理逻辑
parseBeanDefinitions(root, this.delegate);
// 解析后处理,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
}
2.8.1 profile 属性的使用
Spring 多环境支持,略。
2.8.2 解析并注册 BeanDefinition
解析并注册 BeanDefinition 代码
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 对 beans 的处理
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)) {
// SPRING 对 默认标签 beans 的处理
parseDefaultElement(ele, delegate);
}
else {
// SPRING 对 自定义标签 beans 的处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
// SPRING 对 自定义标签 beans 的处理
delegate.parseCustomElement(root);
}
}
}