十五、装载BeanDefinition总结

本文最后更新于:2022年6月11日 下午

装载BeanDefinition总结

该篇是对前边十几篇的总结。

IOC容器初始化的过程:Resource定位BeanDefinition的装载和解析BeanDefinition注册

Resource定位:一般使用外部资源描述bean对象,所以初始化IOC容器第一步需要定位这个外部资源(统一资源加载策略)。

BeanDefinition的装载和解析:装载就是BeanDefinition的载入,通过BeanDefinitionReader读取、解析Resource资源,也就是将用户定义的Bean表示成IOC容器内部的数据结构–BeanDefinition。

注:1.IOC容器内部维护着一个BeanDefinitionMap的数据结构。

​ 2.配置文件中的每一个都对应着一个BeanDefinition对象。

BeanDefinition注册:向IOC容器内部维护的BeanDefinitionMap中注册第二步解析好的BeanDefinition对象,该过程通过BeanDefinitionRegistry接口实现。

注:1.该过程并没有完成依赖注入(Bean创建),Bean创建发生在第一次调用getBean()方法,向容器索取 Bean时,因为默认是懒加载。

​ 2.可以修改加载方式,即设置某个Bean的lazyinit=false,那么这个Bean的依赖注入会在IOC容器初始化的时候完成。

在初始阶段,做过一个实例:

1
2
3
4
5
6
7
8
//①获取资源
ClassPathResource resource = new ClassPathResource("bean.xml");
//②获取 BeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//③根据新建的 BeanFactory 创建一个 BeanDefinitionReader 对象,该 Reader 对象为资源的解析器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//④装载资源
reader.loadBeanDefinitions(resource);

:根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,bean.xml 文件中的内容是我们定义的 Bean 信息。

:创建一个 BeanFactory 。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory 作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立使用的 IoC 容器,它是整个 Spring IoC 的始祖。

:创建 XmlBeanDefinitionReader 读取器,用于载入和解析 BeanDefinition 。

:开始 BeanDefinition 的载入和注册进程,完成后的 BeanDefinition 放置在 IoC 容器中。

Resource定位

Spring为了解决资源定位问题,提供了两个接口:ResourceResourceLoader

Resource:Spring 统一资源的抽象接口。

ResourceLoader:Spring 资源加载的统一抽象。

Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml") 为我们定义了Resource,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:

1
2
3
4
5
6
// XmlBeanDefinitionReader.java
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
//第③步执行了XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//注:DefaultListableBeanFactory是BeanDefinitionRegistry的子类。

直接调用父类 AbstractBeanDefinitionReader 构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;

// 如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver
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();
}
}

BeanDefinition的装载和解析

第④步reader.loadBeanDefinitions(resource);开始BeanDefinition的解析过程:

1
2
3
4
5
//XmlBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}

在这个方法会将资源 Resource 包装成一个 EncodedResource实例对象,然后调用 loadBeanDefinitions(EncodedResource encodedResource) 方法。而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// XmlBeanDefinitionReader.java
// 具体过程查看之前的文章
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// ... 省略一些代码
try {
// 将资源文件转为 InputStream 的 IO 流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 从 InputStream 中得到 XML 的解析源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心逻辑部分,执行加载 BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
// 省略一些代码
}

encodedResource 源中获取 xml 的解析源,然后调用 doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行具体的解析过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
// XmlBeanDefinitionReader.java
// 具体过程查看之前的文章
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// 根据 Document 实例,注册 BeanDefinition 信息
int count = registerBeanDefinitions(doc, resource);
return count;
}
// ... 省略一堆配置
}

在该方法中主要做两件事:

1、调用doLoadDocument(inputSource, resource)根据 xml 解析源获取相应的 Document 对象。

2、调用 registerBeanDefinitions(Document doc, Resource resource) 方法,开启 BeanDefinition 的解析注册过程。

获取相应的Document对象

调用doLoadDocument(inputSource, resource)根据 xml 解析源获取相应的 Document 对象。

1
2
3
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}

五个参数:

1、InputSource:加载 Document 的 Resource 源。

2、EntityResolver:解析文件的解析器,重点,前边已经分析

3、ErrorHandler:处理加载 Document 对象的过程的错误。

4、validationMode:验证模式,重点,前边已经分析,默认自动获取,值为VALIDATION_AUTO,1。

5、namespaceAware:命名空间支持。如果要提供对 XML 名称空间的支持,则为 true ,默认false。

loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,在类 DefaultDocumentLoader 中提供了实现。

1
2
3
4
5
6
7
8
9
10
11
// DefaultDocumentLoader.java
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 创建 DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
// 创建 DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 解析 XML InputSource 返回 Document 对象
return builder.parse(inputSource);
}
解析注册BeanDefinition信息

根据上个步骤转换的Document实例,解析注册 BeanDefinition 信息,该过程调用registerBeanDefinitions(doc, resource);实现:

1
2
3
4
5
6
7
8
9
10
11
12
// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建 BeanDefinitionDocumentReader 对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 获取已注册的 BeanDefinition 数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 创建 XmlReaderContext 对象
// 注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 计算新注册的 BeanDefinition 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}

1、首先,创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader 。
2、然后,调用该 BeanDefinitionDocumentReader 的 registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。

1
2
3
4
5
6
7
8
// DefaultBeanDefinitionDocumentReader.java
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 获得 XML Document Root Element
// 执行注册 BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}
Document对象进行解析

从 Document 对象中获取根元素 root,然后调用 ``doRegisterBeanDefinitions(Element root)` 方法,开启真正的解析过程。

1
2
3
4
5
6
7
8
9
10
11
12
// DefaultBeanDefinitionDocumentReader.java
//前边文章有分析
protected void doRegisterBeanDefinitions(Element root) {
// ... 省略部分代码(非核心)
this.delegate = createDelegate(getReaderContext(), root, parent);
// 解析前处理
preProcessXml(root);
// 解析
parseBeanDefinitions(root, this.delegate);
// 解析后处理
postProcessXml(root);
}

注:preProcessXml(Element root)、#postProcessXml(Element root) 为前置、后置增强处理,目前 Spring 中都是空实现。

parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 是对根元素 root 的解析注册过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// DefaultBeanDefinitionDocumentReader.java
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)) {
parseDefaultElement(ele, delegate);
// 如果该节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(ele);
}
}
}
// 如果根节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(root);
}
}

迭代 root 元素的所有子节点,对其进行判断:

1、若节点为默认命名空间,则调用 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,开启默认标签的解析注册过程。

2、否则,调用 BeanDefinitionParserDelegate.parseCustomElement(Element ele) 方法,开启自定义标签的解析注册过程。

两个过程前边都有介绍。

对默认标签的解析

1
2
3
4
5
6
7
8
9
10
11
12
13
// DefaultBeanDefinitionDocumentReader.java
private 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);
}
}

对四大标签:<import><alias><bean><beans> 进行解析。其中 <bean> 标签的解析为核心工作,解析为BeanDefinition

对自定义标签的解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// BeanDefinitionParserDelegate.java
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取 namespaceUri
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根据 namespaceUri 获取相应的 Handler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用自定义的 Handler 处理
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

获取节点的 namespaceUri,然后根据该 namespaceUri 获取相对应的 NamespaceHandler,最后调用 NamespaceHandler 的 parse(Element element, ParserContext parserContext) 方法,即完成自定义标签的解析和注入。

注册BeanDefinition

将 Document 对象里面的 Bean 标签解析成了一个个的 BeanDefinition 后,下一步则是将这些 BeanDefinition 注册到 IoC 容器中。动作的触发是在解析 Bean 标签完成后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// DefaultBeanDefinitionDocumentReader.java
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 进行 bean 元素解析。
// 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
// 如果解析失败,则返回 null 。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 进行自定义标签处理
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 进行 BeanDefinition 的注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

调用 BeanDefinitionReaderUtils.registerBeanDefinition() 方法来注册。其实,这里面也是调用 BeanDefinitionRegistry 的 .registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,来注册 BeanDefinition 。不过,最终的实现是在 DefaultListableBeanFactory 中实现:

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
// DefaultListableBeanFactory.java
// 所谓注册,就是添加到了一个Map集合,key是beanName,value是BeanDefinition对象。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// ...省略校验相关的代码
// 从缓存中获取指定 beanName 的 BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已经存在
if (existingDefinition != null) {
// 如果存在但是不允许覆盖,抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
} else {
// ...省略 logger 打印日志相关的代码
}
// 【重点】允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 如果未存在
} else {
// ... 省略非核心的代码
// 【重点】添加到 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 重新设置 beanName 对应的缓存
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

总结

现在 IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。

流程图:

IOC容器初始化流程


十五、装载BeanDefinition总结
http://www.muzili.ren/2022/06/11/装载BeanDefinition总结/
作者
jievhaha
发布于
2022年6月11日
许可协议