IoC 之装载 BeanDefinitions 总结
最近时间重新对spring源码进行了解析,以便后续自己能够更好的阅读spring源码,想要一起深入探讨请加我QQ:1051980588
ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
对spring源码解析上面是最基本的几行代码,接下来我会对这基本代码深入探索,当然有些代码解释是基于其他博客借鉴过来的,如有相同希望见谅
ClassPathResource resource = new ClassPathResource("bean.xml");
: 根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,bean.xml
文件中的内容是我们定义的 Bean 信息。DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
:创建一个 BeanFactory 。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory 作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立使用的 IoC 容器,它是整个 Spring IoC 的始祖,在后续会有专门的文章来分析它。XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
:创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition 。reader.loadBeanDefinitions(resource);
:开始 BeanDefinition 的载入和注册进程,完成后的 BeanDefinition 放置在 IoC 容器中。
1. Resource 定位
Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader,其中:
- Resource 接口是 Spring 统一资源的抽象接口
- ResourceLoader 则是 Spring 资源加载的统一抽象。
Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml")
为我们定义了资源,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:
// XmlBeanDefinitionReader.java
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
我们可以看见 直接调用父类 AbstractBeanDefinitionReader 构造方法,代码如下:
// AbstractBeanDefinitionReader.java 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();
}
}
核心在于设置 resourceLoader 这段,如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver ,该类是一个集大成者的 ResourceLoader。
2. BeanDefinition 的载入和解析
reader.loadBeanDefinitions(resource);
代码段,开启 BeanDefinition 的解析过程。如下:
// XmlBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
在这个方法会将资源 resource 包装成一个 EncodedResource 实例对象,然后调用 #loadBeanDefinitions(EncodedResource encodedResource)
方法。而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性。代码如下:
// 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());
}
// ... 具体的读取过程
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
// 省略一些代码
}
从 encodedResource
源中获取 xml 的解析源,然后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,执行具体的解析过程。
// XmlBeanDefinitionReader.java protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// 根据 Document 实例,注册 Bean 信息
int count = registerBeanDefinitions(doc, resource);
return count;
}
// ... 省略一堆配置
}
2.1 转换为 Document 对象
调用在上面方法中 #doLoadDocument(InputSource inputSource, Resource resource)
方法,会将 Bean 定义的资源转换为 Document 对象。代码如下:
// XmlBeanDefinitionReader.java protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
该方法接受五个参数:
inputSource
:加载 Document 的 Resource 源。entityResolver
:解析文件的解析器。errorHandler
:处理加载 Document 对象的过程的错误。validationMode
:验证模式。namespaceAware
:命名空间支持。如果要提供对 XML 名称空间的支持,则为true
#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
方法,在类 DefaultDocumentLoader 中提供了实现。代码如下:
// 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);
}
2.2 注册 BeanDefinition 流程
这到这里,就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了。那么,下一步就是如何将其解析为 SpringIoC 管理的 BeanDefinition 对象,并将其注册到容器中。这个过程由方法 #registerBeanDefinitions(Document doc, Resource resource)
方法来实现。代码如下
// 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 完成。代码如下:
// DefaultBeanDefinitionDocumentReader.java @Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 获得 XML Document Root Element
// 执行注册 BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}
2.2.1 对 Document 对象的解析从 Document 对象中获取根元素 root,然后调用 #doRegisterBeanDefinitions(Element root)` 方法,开启真正的解析过程。代码如下:
// DefaultBeanDefinitionDocumentReader.java protected void doRegisterBeanDefinitions(Element root) {
// ... 省略部分代码(非核心)
this.delegate = createDelegate(getReaderContext(), root, parent); // 解析前处理
preProcessXml(root);
// 解析
parseBeanDefinitions(root, this.delegate);
// 解析后处理
postProcessXml(root); }
#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
是对根元素 root 的解析注册过程。代码如下:
// 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 元素的所有子节点,对其进行判断:
- 若节点为默认命名空间,则调用
#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法,开启默认标签的解析注册过程。详细解析 - 否则,调用
BeanDefinitionParserDelegate#parseCustomElement(Element ele)
方法,开启自定义标签的解析注册过程。
2.2.1.1 默认标签解析
若定义的元素节点使用的是 Spring 默认命名空间,则调用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法,进行默认标签解析。代码如下:
// 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>
进行解析。
2.2.1.2 自定义标签解析
对于默认标签则由 parseCustomElement(Element ele)
方法,负责解析。代码如下:
// 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)
方法,即完成自定义标签的解析和注入。
2.2.2 注册 BeanDefinition
经过上面的解析,则将 Document 对象里面的 Bean 标签解析成了一个个的 BeanDefinition ,下一步则是将这些 BeanDefinition 注册到 IoC 容器中。动作的触发是在解析 Bean 标签完成后,代码如下
// 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 的注册
// Register the final decorated instance.
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 中实现,代码如下:
// DefaultListableBeanFactory.java
@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);
}
}
这段代码最核心的部分是这句 this.beanDefinitionMap.put(beanName, beanDefinition)
代码段。所以,注册过程也不是那么的高大上,就是利用一个 Map 的集合对象来存放:key
是 beanName
,value
是 BeanDefinition 对象
3. 小结
至此,整个 IoC 的初始化过程就已经完成了,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IoC 容器中,都已经完美地完成了。现在 IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。最后用一张流程图来结束这篇总结之文。
IoC 之装载 BeanDefinitions 总结的更多相关文章
- Spring框架——IOC 自动装载
IOC自动装载有两种形式 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=" ...
- Spring IOC以及三种注入方式
IOC是spring的最基础部分,也是核心模块,Spring的其他组件模块和应用开发都是以它为基础的.IOC把spring的面向接口编程和松耦合的思想体现的淋漓尽致. IOC概念 IOC(Invers ...
- 案例学编程系列:案例认识 Spring IOC
本文spring libs 地址:https://github.com/yizhiamumu/springlibs Spring 能帮我们做什么 ①.Spring 能帮我们根据配置文件创建及组装对象之 ...
- Sping框架的IOC特性 悲观锁、乐观锁 Spring的AOP特性
Sping框架的IOC特性 IOC(Inversion of Control):控制反转 以下以课程与老师的安排来介绍控制反转. 一个合理的课程编排系统应该围绕培训的内容为核心,而不应该以具体的培训老 ...
- Sping框架的IOC特性
IOC(Inversion of Control):控制反转 以下以课程与老师的安排来介绍控制反转. 一个合理的课程编排系统应该围绕培训的内容为核心,而不应该以具体的培训老师为核心,这样才能在正常授课 ...
- Spring核心 IoC和AOP原理
1. 什么是Spring Spring是一个轻量的Java开源框架,它简化了应用开发,实现基于POJO的编程模型.它的两大核心是:IoC(控制反转),AOP(面向切面编程). 2. IoC控制反转 简 ...
- Spring学习笔记之Spring概述
概述 Spring是一个java应用最广的开源框架,它是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Deve ...
- Java秋招面试复习大纲(二):Spring全家桶+MyBatis+MongDB+微服务
前言 对于那些想面试高级 Java 岗位的同学来说,除了算法属于比较「天方夜谭」的题目外,剩下针对实际工作的题目就属于真正的本事了,热门技术的细节和难点成为了面试时主要考察的内容. 这里说「天方夜谭」 ...
- 【spring源码系列】之【xml解析】
1. 读源码的方法 java程序员都知道读源码的重要性,尤其是spring的源码,代码设计不仅优雅,而且功能越来越强大,几乎可以与很多开源框架整合,让应用更易于专注业务领域开发.但是能把spring的 ...
随机推荐
- 【Nginx】实现负载均衡的几种方式
要理解负载均衡,必须先搞清楚正向代理和反向代理. 注: 正向代理,代理的是用户. 反向代理,代理的是服务器 什么是负载均衡 当一台服务器的单位时间内的访问量越大时,服务器压力就越大,大到超过自身承受能 ...
- Docker搭建disconf环境,三部曲之一:极速搭建disconf
Docker下的disconf实战全文链接 <Docker搭建disconf环境,三部曲之一:极速搭建disconf>: <Docker搭建disconf环境,三部曲之二:本地快速构 ...
- jQuery的大小拖动
原来一直没有拖动放大的效果,居然只是没有jquery-ui.css的样式文件,虽然这是一个很小的部分,但是防微杜渐.程序本来就是个不容有小错误的存在. 如果没有样式文件,那么可以收缩的三角形就不会出现 ...
- MySQL和mybatis查询相关
0.mybatis的xml文件头 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapp ...
- Java IO写文件效率
写入方法: /** *1 按字节写入 FileOutputStream * * @param count 写入循环次数 * @param str 写入字符串 */ public void output ...
- Hessian 接口使用示例总结(转载)
一.使用hessian接口准备 首先,hessian接口的使用,必须要准备hessian接口的jar包,本文使用的jar包如下:hessian-4.0.7.jar; Hessian接口的使用一般是在两 ...
- OPC-System.Runtime.InteropServices.COMException:“Error HRESULT E_FAIL has been returned from a call to a COM component.”
Error HRESULT E_FAIL has been returned from a call to a COM component的错误提示,还是因为OPC配置,这次是红框中标签定义错误,应该 ...
- 松软科技课堂:索引器计时器Timer
在.NET中有三种计时器:1.System.Windows.Forms命名空间下的Timer控件,它直接继承自Componet.Timer控件只有绑定了Tick事件和设置Enabled=True后才会 ...
- 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业
前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...
- ReactNative之Redux详解
用redux有一段时间了,感觉还是有必要把其相关的知识点系统的总结一下的,毕竟好记性不如烂笔头.上篇博客更新了关于<ES6中的迭代器.Generator函数以及Generator函数的异步操作& ...