【十三】图解 Spring 核心数据结构:BeanDefinition 其二

news2025/4/22 8:51:22

图解 Spring 核心数据结构:BeanDefinition 其二

概述

        前面写过一篇相关文章作为开篇介绍了一下BeanDefinition,本篇将深入细节来向读者展示BeanDefinition的设计,让我们一起来揭开日常开发中使用的bean的神秘面纱,深入细节透彻理解spring bean的概念。

一、BeanDefinition的加载过程

        首先我们来复习一下spring 容器的类的继承体系:

可以看到这是一个庞大的类继承体系,上面都是实现的接口,第一个实现类是AbstractApplicationContext,这里面有一个关键的方法fefresh(),该方法为 spring 启动入口,本文重点不是梳理这个方法,如果有需要可以留言后续出文章进行详细分析。  

 @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            // 初始化前的准备工作,比如系统属性、环境变量的准备及验证
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            // 初始化BeanFactory,并进行XML文件解析
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            // 准备bean和非bean
            // BeanFactory各种功能的填充,比如对@Qualifier和@Autowired注解的支持
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                // 扩展点,具体功能由子类实现
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                // 激活各种BeanFactory处理器
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                // 注册拦截Bean创建的后处理器,这里只是注册,真正的调用在getBean的时候
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                // 国际化处理
                // 为上下文初始化Message源,即不同语音的消息体
                initMessageSource();

                // Initialize event multicaster for this context.
                // 初始化应用消息广播器,并初始化"applicationEventMulticaster"bean
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                // 模版方法,由子类扩展
                onRefresh();

                // Check for listener beans and register them.
                // 在所有注册的bean中查找Listener bean,注册到消息广播器中
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                // 对非惰性的单例进行初始化
                // 一般情况下单例都会在这里就初始化了,除非指定了惰性加载
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,
                // 同时发出ContextRefreshedEvent时间通知别人
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }
    

这里我们主要关注obtainFreshBeanFactory()方法,这个方法是初始化BeanFactory的,在初始化的过程中会解析BeanDefinition,为了继续分析BeanDefinition的解析,这时候我们需要忽略其他的一些实现逻辑,我们在AbstractRefreshableApplicationContext中找到一个抽象方法:

	/**
	 * Load bean definitions into the given bean factory, typically through
	 * delegating to one or more bean definition readers.
	 * @param beanFactory the bean factory to load bean definitions into
	 * @throws BeansException if parsing of the bean definitions failed
	 * @throws IOException if loading of bean definition files failed
	 * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 */
	protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws BeansException, IOException;

看注释就知道这个方法是加载BeanDefinition的,我们找到了方法实现类:AnnotationConfigWebApplicationContext和XmlBeanDefinitionReader,可以明显的看出来这两个实现类一个是解析注解方式的,一个是xml配置方式的,限于篇幅这里分析一下经典的xml方式。

        如下是XmlBeanDefinitionReader中的实现:

	/**
	 * Loads the bean definitions via an XmlBeanDefinitionReader.
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 * @see #initBeanDefinitionReader
	 * @see #loadBeanDefinitions
	 */
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

这里首先定义了一个xml文件读取类XmlBeanDefinitionReader,,之后继续跟进源码:

/**
	 * Load the bean definitions with the given XmlBeanDefinitionReader.
	 * <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method;
	 * therefore this method is just supposed to load and/or register bean definitions.
	 * <p>Delegates to a ResourcePatternResolver for resolving location patterns
	 * into Resource instances.
	 * @throws IOException if the required XML document isn't found
	 * @see #refreshBeanFactory
	 * @see #getConfigLocations
	 * @see #getResources
	 * @see #getResourcePatternResolver
	 */
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

可以看到这里是循环遍历从配置的目录中加载BeanDefinition,继续跟进loadBeanDefinitions()方法

,我们找到了实际加载BeanDefinition的方法:

/**
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 * @see #doLoadDocument
	 * @see #registerBeanDefinitions
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

继续往下跟进我们到了Bean注册的方法中:

/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		BeanDefinitionParserDelegate current = createDelegate(getReaderContext(), root, parent);
		this.delegate = current;

		if (current.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				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);
		parseBeanDefinitions(root, current);
		postProcessXml(root);

		this.delegate = parent;
	}

这里我们关注一下这个方法:

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

这里就是根据xml中配置的标签元素来进行处理的,我们可以打开BeanDefinitionParserDelegate

类,发现我们找到了xml中标签元素的定义了。

        到这里主流程基本讲完了细节不再拖沓了,直接进入DefaultListableBeanFactory类中,到此我们发现这样一行代码:

this.beanDefinitionMap.put(beanName, beanDefinition);

原来我们的bean最终都是存放在map中的:

/** Map of bean definition objects, keyed by bean name. */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

二、BeanDefinition的详细分析

        经过上一节漫长的更近源码分析,我们终于知道了平时使用的bean的加载和存储细节了,到这里我们需要进一步追问spirng BeanDefinition 存放了哪些信息呢?这样才算是真正的理解了bean。

Spring的BeanDefinition用于描述Spring Bean的元数据信息,它包含了以下主要信息:

  1. Bean的类名:定义了Bean的实际类是什么。

  2. Bean的作用域:例如singletonprototyperequestsession等。

  3. Bean的依赖关系:其他Bean作为构造函数或者设置方法的参数。

  4. Bean的lazy初始化标志:表示Bean是否在容器启动时就被实例化。

  5. Bean的自动装配模式:如按类型自动装配、按名称自动装配。

  6. Bean的工厂方法:如果Bean是通过FactoryBean创建的,这里会记录工厂方法的名称。

  7. Bean的属性:包括构造函数参数、属性值等。

  8. Bean的初始化方法和销毁方法:指定Bean的初始化和销毁时调用的方法。

这些信息在Spring容器的Bean定义阶段被解析和存储的,用于之后Bean的实例化和依赖注入等阶段。

总结

        花费了两个小时总算是把Spring 核心数据结构:BeanDefinition讲解清晰了,写文章既需要对所写文章技术点有深入的了解还需要耐心。这些内容也都不是什么新的事物了,但是每个人还是需要自己去跟进一遍源码并结合自身的知识去分析消化一下才能更深入的理解到,希望文章能够给读者有一定的帮助。

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

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

相关文章

django之url路径

方式一&#xff1a;path 语法&#xff1a;<<转换器类型:自定义>> 作用&#xff1a;若转换器类型匹配到对应类型的数据&#xff0c;则将数据按照关键字传参的方式传递给视图函数 类型&#xff1a; str: 匹配除了”/“之外的非空字符串。 /test/zvxint: 匹配0或任何…

剖析DeFi交易产品之UniswapV3:交易路由合约

本文首发于公众号&#xff1a;Keegan小钢 SwapRouter 合约封装了面向用户的交易接口&#xff0c;但不再像 UniswapV2Router 一样根据不同交易场景拆分为了那么多函数&#xff0c;UniswapV3 的 SwapRouter 核心就只有 4 个交易函数&#xff1a; exactInputSingle&#xff1a;指…

go语言day10 接口interface 类型断言 type关键字

接口&#xff1a; 空接口类型&#xff1a; 要实现一个接口&#xff0c;就要实现该接口中的所有方法。因为空接口中没有方法&#xff0c;所以自然所有类型都实现了空接口。那么就可以使用空接口类型变量去接受所有类型对象。 类比java&#xff0c;有点像Object类型的概念&#x…

基于YOLOv10+YOLOP+PYQT的可视化系统,实现多类别目标检测+可行驶区域分割+车道线分割【附代码】

文章目录 前言视频效果必要环境一、代码结构1、 训练参数解析2、 核心代码解析1.初始化Detector类2. torch.no_grad()3. 复制输入图像并初始化计数器4. 调用YOLOv10模型进行目标检测5. 提取检测结果信息6. 遍历检测结果并在图像上绘制边界框和标签7. 准备输入图像以适应End-to-…

2024年最新运维面试题(附答案)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 一&#xff0e;选择题 1.HTTP协议默认使用哪个端口…

DolphinScheduler-3.1.9 资源中心实践

前言 目前DolphinScheduler最新的稳定版本是 3.1.9 &#xff0c;基于此做些探索&#xff0c;逐渐深化学习路径&#xff0c;以便于加深理解。 3.2.1 是最新的版本。目前的稳定版本是 3.1.9 基础环境&#xff1a;Hadoop3.3, Java 8, Python3, MacOS14.2.1 一、本地伪分布式安装…

当需要对多个表进行联合更新操作时,怎样确保数据的一致性?

文章目录 一、问题分析二、解决方案三、示例代码&#xff08;以 MySQL 为例&#xff09;四、加锁机制示例五、测试和验证六、总结 在数据库管理中&#xff0c;经常会遇到需要对多个表进行联合更新的情况。这种操作带来了一定的复杂性&#xff0c;因为要确保在整个更新过程中数据…

Linux多进程和多线程(六)进程间通信-共享内存

多进程(六) 共享内存共享内存的创建 示例: 共享内存删除 共享内存映射 共享内存映射的创建解除共享内存映射示例:写入和读取共享内存中的数据 写入: ### 读取: 大致操作流程: 多进程(六) 共享内存 共享内存是将分配的物理空间直接映射到进程的⽤户虚拟地址空间中, 减少数据在…

C++笔试强训2

文章目录 一、选择题二、编程题 一、选择题 和笔试强训1的知识点考的一样&#xff0c;因为输出的是double类型所以后缀为f,m.n对其30个字符所以m是30&#xff0c;精度是4所以n是4&#xff0c;不加符号默认是右对齐&#xff0c;左对齐的话前面加-号&#xff0c;所以答案是-30.4f…

人工智能开发中的数据隐私

人工智能开发中的数据隐私对于建立用户信任和遵守严格法规至关重要。保护敏感信息可确保合乎道德的人工智能使用并防止有害的数据泄露。 为什么在人工智能开发中优先考虑数据隐私至关重要 人工智能的迅猛发展开启了一个前所未有的技术进步时代&#xff0c;彻底改变了各行各业&…

价格预言机的使用总结(一):Chainlink篇

文章首发于公众号&#xff1a;Keegan小钢 前言 价格预言机已经成为了 DeFi 中不可获取的基础设施&#xff0c;很多 DeFi 应用都需要从价格预言机来获取稳定可信的价格数据&#xff0c;包括借贷协议 Compound、AAVE、Liquity &#xff0c;也包括衍生品交易所 dYdX、PERP 等等。…

STL—容器—string类【对其结构和使用的了解】【对oj相关练习的训练】

STL—容器—string类 其实string类准确来说并不是容器&#xff0c;因为他出现的时间比STL要早&#xff0c;但是也可以说是容器吧。 1.为什么要学习string类&#xff1f; 1.1C语言当中的字符串 C语言中&#xff0c;字符串是以’\0’结尾的一些字符的集合&#xff0c;为了操作…

ROS——坐标系管理、监听与广播、常用可视化工具

坐标系管理 TF功能包 小海龟追踪实验 ros版本(20.04)的tf安装命令: sudo apt-get install ros-noetic-turtle-tf 解决因python版本出现的无法生成跟随海龟&#xff1a; sudo ln -s /usr/bin/python3 /usr/bin/python ( -s 软链接,符号链接) ln命令&#xff08;英文全拼&#…

【C++】B树及其实现

写目录 一、B树的基本概念1.引入2.B树的概念 二、B树的实现1.B树的定义2.B树的查找3.B树的插入操作4.B树的删除5.B树的遍历6.B树的高度7.整体代码 三、B树和B*树1.B树2.B*树3.总结 一、B树的基本概念 1.引入 我们已经学习过二叉排序树、AVL树和红黑树三种树形查找结构&#x…

数据库7.4

第二次作业 1.登陆数据库 2.创建数据库zoo 3.修改数据库zoo字符集为gbk 4.选择当前数据库为zoo 5.查看创建数据库zoo信息 6.删除数据库zoo C:\Windows\System32>mysql -uroot -p20040830Nmx mysql> create database zoo; alter database zoo character set gbk; mys…

InspireFace-商用级的跨平台开源人脸分析SDK

InspireFace-商用级的跨平台开源人脸分析SDK InspireFaceSDK是由insightface开发的⼀款⼈脸识别软件开发⼯具包&#xff08;SDK&#xff09;。它提供了⼀系列功能&#xff0c;可以满⾜各种应⽤场景下的⼈脸识别需求&#xff0c;包括但不限于闸机、⼈脸⻔禁、⼈脸验证等。 该S…

【C++/STL】优先级队列的介绍与模拟实现仿函数

✨ 万物与我皆是自由诗 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1…

非对称加密算法原理与应用2——RSA私钥加密文件

作者:私语茶馆 1.相关章节 (1)非对称加密算法原理与应用1——秘钥的生成-CSDN博客 第一章节讲述的是创建秘钥对,并将公钥和私钥导出为文件格式存储。 本章节继续讲如何利用私钥加密内容,包括从密钥库或文件中读取私钥,并用RSA算法加密文件和String。 2.私钥加密的概述…

VSCode设置好看清晰的字体!中文用鸿蒙,英文用Jetbrains Mono

一、中文字体——HarmonyOS Sans SC 1、下载字体 官网地址&#xff1a;https://developer.huawei.com/consumer/cn/design/resource/ 直接下载&#xff1a;https://communityfile-drcn.op.dbankcloud.cn/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20230517…

[学习笔记]SQL学习笔记(连载中。。。)

学习视频&#xff1a;【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库 目录 1.SQL的基础知识1.1.表(table)和键(key)1.2.外键、联合主键 2.MySQL安装&#xff08;略&#xff0c;请自行参考视频&#xff09;3.基本的MySQL语法3.1.规…