spring源码 IoC 之xml配置bean注册解析的 BeanDefinitions

news2024/11/26 17:44:50

概述

IoC 容器的初始化过程分为三步骤:Resource 定位、BeanDefinition 的载入和解析,BeanDefinition 注册

1、Resource 定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IoC 容器的第一步就是需要定位这个外部资源

2、BeanDefinition 的装载和解析。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition
2.1、在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构
2.2、在配置文件中每一个 都对应着一个 BeanDefinition 对象。

BeanDefinition 注册。向 IoC 容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistry 接口来实现的。在 IoC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的

xml解析

ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

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 容器中

// XmlBeanDefinitionReader.java
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
	super(registry);
}

调用父类的构造方法
在这里插入图片描述

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();
		}
	}

BeanDefinition 的载入和解析

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

在这个方法会将资源 resource 包装成一个 EncodedResource 实例对象,然后调用 #loadBeanDefinitions(EncodedResource encodedResource) 方法。而将 Resource 封装成 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);
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
// 从 InputStream 中得到 XML 的解析源
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			//读取过程
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

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

=
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;
	}

}

在该方法中主要做两件事:
1、根据 xml 解析源获取相应的 Document 对象,采用SAX解析
2、调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,开启 BeanDefinition 的解析注册过程。

// 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);
}

将定义的 Bean 资源文件,载入并转换为 Document 对象了。下一步将其解析为 SpringIoC 管理的 BeanDefinition 对象,并将其注册到容器中。这个过程由方法 #registerBeanDefinitions(Document doc, Resource resource) 方法来实现。代码如下:

注册 BeanDefinition 流程

// 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;
}

首先,创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader 。
然后,调用该 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());
}

对 Document 对象的解析

// DefaultBeanDefinitionDocumentReader.java

protected void doRegisterBeanDefinitions(Element root) {
    // ... 省略部分代码(非核心)
    this.delegate = createDelegate(getReaderContext(), root, parent);

    // 解析前处理
    preProcessXml(root);
    // 解析
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理
    postProcessXml(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) 方法,开启自定义标签的解析注册过程

默认标签解析

// 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);
	}
}

对四大标签:、、、 进行解析。其中 标签的解析为核心工作

注册 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 对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/621231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

opencv4 傅里叶变换

傅里叶变换 ① 高频&#xff1a;变化剧烈的灰度分量&#xff0c;例如边界礁石。 ② 低频&#xff1a;变化缓慢的灰度分量&#xff0c;例如一片大海。 ③ 高通滤波器&#xff1a;只保留高频&#xff0c;会使得图像细节增强。高频边界锐化了&#xff0c;增强了&#xff0c;细节…

【群智能算法改进】一种改进的算术优化算法 改进算术优化算法 改进AOA[2]【Matlab代码#38】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始AOA2. 改进后的MAOA算法2.1. Kent映射种群初始化2.2 复合摆线法优化MOA参数2.3 种群top20%精英变异和Cauchy变异组合2.3.1 麻雀精英变异2.3.2 柯西变异 3. 部分代码展示4. 仿真结果展示5. 资源获取说明 【获取资…

高性能软件负载OpenResty整合Reids集群配置

目录 1 OpenResty整合Reids集群配置1.1 下载安装lua_resty_redis1.1.1 连接Redis集群封装1.1.2 配置lua脚本路径1.1.3 测试脚本 1.2 请求参数封装1.2.1 测试脚本 1.3 抓取模板内容封装1.3.1 下载安装lua-resty-http1.3.2 测试脚本 1.4 模版渲染配置1.4.1 下载安装lua-resty-tem…

基于RK3399/RK3588 H.265/HEVC的低延迟视频传输系统设计与实现

近年来&#xff0c;随着短视频直播的兴起&#xff0c;视频传输设备在生活中的应用越发普及。人们对图像 清晰度、帧率、码率等技术指标的要求不断提高&#xff0c;视频帧所包含的数据量也在急速增加。在 有限的网络带宽下&#xff0c;传统的视频采集设备面临压缩率不足、帧率…

Ui自动化测试如何上传文件

前言 实施UI自动化测试的时候&#xff0c;经常会遇见上传文件的操作&#xff0c;那么对于上传文件你知道几种方法呢&#xff1f;今天我们就总结一下几种常用的上传文件的方法&#xff0c;并分析一下每个方法的优点和缺点以及哪种方法效率&#xff0c;稳定性更高 被测HTML代码…

python基础知识(九):函数

目录 1. 函数的定义2. 传递实参2.1 位置实参2.2 关键字实参2.3 默认值2.4 传递任意数量的实参2.5 结合使用位置实参和任意数量实参 3. 导入模块3.1 导入特定的函数3.2 使用 as 给函数指定别名3.3 使用 as 给模块指定别名3.4 导入模块中的所有函数 1. 函数的定义 函数的定义形式…

Wijmo 5.20231.888 JavaScript UI Crack

Wijmo使用更快、更灵活的 JavaScript UI 组件构建更好的应用程序 使用 Wijmo&#xff0c;利用我们引人注目的 UI 组件库&#xff0c;将更多时间花在应用程序的核心功能上。要求零依赖&#xff0c;Wijmo sports弹性网格&#xff0c;业内最好的 JavaScript 数据网格&#xff0c;提…

基于深度学习的高精度动物检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度动物检测识别系统可用于日常生活中或野外来检测与定位动物目标&#xff08;狼、鹿、猪、兔和浣熊&#xff09;&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的动物&#xff08;狼、鹿、猪、兔和浣熊&#xff09;目标检测识…

前端基础面试题(HTML,CSS,JS)

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 html语义化的理解 代码结构: 使页面在没有css的情况下,也能够呈现出好的内容结构 有利于SE…

AI实战营:MMPreTrain代码实现

环境 环境安装 pip install openmim mim install mmengine mim install mmcv mim install mmpretrain # 安装多模态模型 pip install "mmpretrain[multimodal]" 验证环境 In [1]: import mmengineIn [2]: mmengine.__version__ Out[2]: 0.7.3In [3]: import …

开发者出海合规手册;@levelsio独立开发月入20万解析;MJ+AR设计珠宝;SD算法原理-通俗版 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 独立开发者必看&#xff0c;出海应用开发者合规手册 这是 JourneymanChina 多年出海经验教训的总结&#xff0c;适用于Google Play 以…

在Wamp环境中如何下载Composer并且使用Laravel配置Apache服务器

一.Composer的安装 方法1.到Composer官网Composer (getcomposer.org)下载 点击Composer-Setup.exe下载Composer安装包 点击Next 这里选择你的php.exe的地址 然后一直点next结束。 然后打开cmd命令输入composer -v看是否运行成功。 方法2.CMD命令安装composer php -r &quo…

学生考试作弊检测系统 yolov8

学生考试作弊检测系统采用yolov8网络模型人工智能技术&#xff0c;学生考试作弊检测系统过在考场中安装监控设备&#xff0c;对学生的作弊行为进行实时监测。当学生出现作弊行为时&#xff0c;学生考试作弊检测系统将自动识别并记录信息。YOLOv8 算法的核心特性和改动可以归结为…

SolVES 模型与多技术融合【多语言】实现生态系统服务功能社会价值评估及拓展案例分析

生态系统服务是人类从自然界中获得的直接或间接惠益&#xff0c;可分为供给服务、文化服务、调节服务和支持服务4类&#xff0c;对提升人类福祉具有重大意义&#xff0c;且被视为连接社会与生态系统的桥梁。自从启动千年生态系统评估项目&#xff08;Millennium Ecosystem Asse…

IDEA中Maven依赖包下载不了的一种“奇怪”解决方案【亲测有效】

&#x1f4a7; 记录一下今天遇到的 b u g \color{#FF1493}{记录一下今天遇到的bug} 记录一下今天遇到的bug&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 数据结构与算法…

Linux - fd文件描述符和文件详解

​​​​​​​ ​​​​​​​ ​​​​​​​ 感谢各位 点赞 收藏 评论 三连支持 本文章收录于专栏【Linux系统编程】 ❀希望能对大家有所帮助❀ 本文章由 风君子吖 原创 ​​​​​​​ ​​​​​​​ ​​​​​​​ …

WPF 如何实时查看页面元素如何使用实时可视化树

文章目录 往期回顾可视化页面元素如何使用调试工具 总结 往期回顾 WPF 学习&#xff1a;如何使用实时可视化树&#xff0c;照着MaterialDesign的Demo学习 可视化页面元素 我们知道&#xff0c;网页的页面元素是可以通过按F12查看代码。查看到页面元素的。 WPF也有类似的工具…

基于相位共轭法的散射聚焦成像研究-Matlab代码

▒▒本文目录▒▒ 一、引言二、相位共轭法散射聚焦成像Matlab仿真三、参考文献四、Matlab程序开发与实验指导 一、引言 一直以来&#xff0c;研究人员致力于分析造成散射的原因、随机介质性质以及各种散射光的特征&#xff0c;并且研究透过散射介质成像。1990年&#xff0c;I.…

模型剪枝:给模型剪个头发

本文来自公众号“AI大道理”。 深度学习网络模型从卷积层到全连接层存在着大量冗余的参数&#xff0c;大量神经元激活值趋近于0&#xff0c;将这些神经元去除后可以表现出同样的模型表达能力&#xff0c;这种情况被称为过参数化&#xff0c;而对应的技术则被称为模型剪枝。 网…

在价格战中苦苦挣扎的小鹏汽车和蔚来,哪个是最好的电动汽车股?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结&#xff1a; 从长期来看&#xff0c;小鹏汽车(XPEV)的基本面优于蔚来(NIO)&#xff0c;小鹏汽车目前的估值也更有吸引力&#xff0c;在全球电动汽车行业中也具有更好的长期投机性。 猛兽财经的投资组合中其中有一部分…