Spring BeanDefinition 也分父子关系?

news2024/11/20 12:31:28

在 Spring 框架中,BeanDefinition 是一个核心概念,用于定义和配置 bean 的元数据,虽然在实际应用中,我们一般并不会或者很少直接定义 BeanDefinition,但是,我们在 XML 文件中所作的配置,以及利用 Java 代码做的各种 Spring 配置,都会被解析为 BeanDefinition,然后才会做进一步的处理。BeanDefinition 允许开发人员以一种声明性的方式定义和组织 bean,这里有很多属性,今天松哥单纯的来和小伙伴们聊一聊它的 parentName 属性,parentName 属性在 BeanDefinition 中扮演着重要的角色,用于建立 bean 之间的父子关系。

之前松哥有一篇文章和小伙伴们聊了 BeanFactory 之间的父子关系(Spring 中的父子容器是咋回事?),大家注意和今天的内容进行区分,今天我们聊的是 BeanDefinition 之间的父子关系。

BeanDefinition 的 parentName 属性的主要功能是允许我们在创建一个 bean 的同时,能够继承另一个已经定义好的 bean。通过指定 parentName 属性,我们可以重用已有 bean 的配置,并在此基础上进行修改或扩展。

先不废话了,我先来举两个例子,小伙伴们先感受一下 BeanDefinition 的作用。

1. 实践

假设我有如下两个类,首先是一个动物的基类,如下:

public class Animal {
    private String name;
    private Integer age;
    //省略 getter/setter
}

然后有一个 Dog 类,如下:

public class Dog {
    private String name;
    private Integer age;
    private String color;
    //省略 getter/setter
}

小伙伴们注意,这里的 Dog 类并没有继承自 Animal 类,但是有两个跟 Animal 同名的属性。之所以这样设计是希望小伙伴们理解 BeanDefinition 中的 parentName 属性和 Java 中的继承并无关系,虽然大部分情况下我们用到 parentName 的时候,Java 中相关的类都是继承关系。

现在,有一些通用的属性我想在 Animal 中进行配置,Dog 中特有的属性则在 Dog 中进行配置,我们来看下通过 XML 和 Java 分别该如何配置。

1.1 XML 配置

<bean id="animal" class="org.javaboy.demo.p2.Animal">
    <property name="name" value="小黑"/>
    <property name="age" value="3"/>
</bean>
<bean class="org.javaboy.demo.p2.Dog" id="dog" parent="animal">
    <property name="color" value="黑色"/>
</bean>

小伙伴们看到,首先我们配置 Animal,Animal 中有 name 和 age 两个属性,然后我又配置了 Dog Bean,并未之指定了 parent 为 animal,然后给 Dog 设置了 color 属性。

现在,Dog Bean 定义出来的 BeanDefinition 中将来就包含了 animal 中的属性值。

1.2 Java 配置

再来看看 Java 配置该如何写。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
RootBeanDefinition pbd = new RootBeanDefinition();
MutablePropertyValues pValues = new MutablePropertyValues();
pValues.add("name", "小黄");
pbd.setBeanClass(Animal.class);
pbd.setPropertyValues(pValues);
GenericBeanDefinition cbd = new GenericBeanDefinition();
cbd.setBeanClass(Dog.class);
cbd.setParentName("parent");
MutablePropertyValues cValues = new MutablePropertyValues();
cValues.add("name", "小强");
cbd.setPropertyValues(cValues);
ctx.registerBeanDefinition("parent", pbd);
ctx.registerBeanDefinition("child", cbd);
ctx.refresh();
Dog child = (Dog) ctx.getBean("child");
System.out.println("child = " + child);

这里我使用了 RootBeanDefinition 来做 parent,其实从名字上就能看出来 RootBeanDefinition 适合做 parent,并且 RootBeanDefinition 不能作为 child。强行设置运行时会抛出异常,RootBeanDefinition#setParentName 方法如下:

@Override
public void setParentName(@Nullable String parentName) {
	if (parentName != null) {
		throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
	}
}

MutablePropertyValues 是为相应的对象设置属性值。

child 我这里使用了 GenericBeanDefinition,这个主要是做 child 的处理,最早有一个专门做 child 的 ChildBeanDefinition,不过自从 Spring2.5 开始提供了 GenericBeanDefinition 之后,现在用来做 child 首选 GenericBeanDefinition。

在上述案例中,parent 和 child 都设置了 name 属性,那么 child 会覆盖掉 parent,这一点和 Java 中的继承一致。

用法就是这样,并不难。

这就是 Spring BeanDefinition 中的父子关系问题。

2. 源码分析

那么接下来我们也把这块的源码稍微来分析一下。

简便起见,我们就不从 Bean 的创建开始分析了,直接来看和 BeanDefinition 中 parentName 属性相关的地方,但是前面涉及到的方法还是给小伙伴们梳理一下,就是下图:

那么这里涉及到的关键方法其实就是 AbstractBeanFactory#getMergedBeanDefinition:

protected RootBeanDefinition getMergedBeanDefinition(
		String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
		throws BeanDefinitionStoreException {

	synchronized (this.mergedBeanDefinitions) {
		RootBeanDefinition mbd = null;
		RootBeanDefinition previous = null;

		// Check with full lock now in order to enforce the same merged instance.
		if (containingBd == null) {
			mbd = this.mergedBeanDefinitions.get(beanName);
		}

		if (mbd == null || mbd.stale) {
			previous = mbd;
			if (bd.getParentName() == null) {
				// Use copy of given root bean definition.
				if (bd instanceof RootBeanDefinition rootBeanDef) {
					mbd = rootBeanDef.cloneBeanDefinition();
				}
				else {
					mbd = new RootBeanDefinition(bd);
				}
			}
			else {
				// Child bean definition: needs to be merged with parent.
				BeanDefinition pbd;
				try {
					String parentBeanName = transformedBeanName(bd.getParentName());
					if (!beanName.equals(parentBeanName)) {
						pbd = getMergedBeanDefinition(parentBeanName);
					}
					else {
						if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent) {
							pbd = parent.getMergedBeanDefinition(parentBeanName);
						}
						else {
							throw new NoSuchBeanDefinitionException(parentBeanName,
									"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
											"': cannot be resolved without a ConfigurableBeanFactory parent");
						}
					}
				}
				// Deep copy with overridden values.
				mbd = new RootBeanDefinition(pbd);
				mbd.overrideFrom(bd);
			}

			// Set default singleton scope, if not configured before.
			if (!StringUtils.hasLength(mbd.getScope())) {
				mbd.setScope(SCOPE_SINGLETON);
			}

			// A bean contained in a non-singleton bean cannot be a singleton itself.
			// Let's correct this on the fly here, since this might be the result of
			// parent-child merging for the outer bean, in which case the original inner bean
			// definition will not have inherited the merged outer bean's singleton status.
			if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
				mbd.setScope(containingBd.getScope());
			}

			// Cache the merged bean definition for the time being
			// (it might still get re-merged later on in order to pick up metadata changes)
			if (containingBd == null && isCacheBeanMetadata()) {
				this.mergedBeanDefinitions.put(beanName, mbd);
			}
		}
		if (previous != null) {
			copyRelevantMergedBeanDefinitionCaches(previous, mbd);
		}
		return mbd;
	}
}

这个方法看名字就是要获取一个合并之后的 BeanDefinition,就是将 child 中的属性和 parent 中的属性进行合并,然后返回,这个方法中有一个名为 mbd 的变量,这就是合并之后的结果。

  1. 首先会尝试从 mergedBeanDefinitions 变量中获取到合并之后的 BeanDefinition,mergedBeanDefinitions 相当于就是一个临时缓存,如果之前已经获取过了,那么获取成功之后就将之保存到 mergedBeanDefinitions 中,如果是第一次进入到该方法中,那么该变量中就没有我们需要的数据,所以会继续执行后面的步骤。
  2. 当第 1 步并未拿到 mbd 的时候,接下来继续判断 bd.getParentName() 是否为空,这个其实就是查看当前的 BeanDefinition 是否有设置 parentName,如果有设置,这里获取到的就不为 null,否则为 null。如果这里获取到的值为 null,那么就会根据当前传入的 BeanDefinition 生成一个 mbd,至于具体的生成方式:如果传入的 BeanDefinition 是 RootBeanDefinition 类型的,则调用 clone 方法去生成 mbd(本质上也是 new 一个新的 RootBeanDefinition),如果传入的 BeanDefinition 不是 RootBeanDefinition 类型的,则直接 new 一个新的 RootBeanDefinition,在 new 的过程中,会把传入的 BeanDefinition 上的属性都复制到新的 RootBeanDefinition 中。
  3. 如果 bd.getParentName() 不为空,则意味着存在 parent BeanDefinition,所以就要进行合并处理了,合并时候又有一个小细节,如果 parentBeanName 等于当前的 beanName,由于 Spring 在同一个容器中不允许存在同名的 bean,所以这就说明 parentBeanName 可能是父容器的 Bean,此时就要去父容器中去处理,当然最终调用到的还是当前方法,关于父子容器这一块,小伙伴们可以参考松哥之前的 Spring 中的父子容器是咋回事? 一文。如果 parentBeanName 不等于当前 beanName,那么现在就可以调用 getMergedBeanDefinition 方法去获取到 parentBeanDefinition 了,getMergedBeanDefinition 是当前方法的重载方法,该方法最终也会调用到当前方法,原因就在于 parentBeanDefinition 本身也可能存在 parentBeanDefinition。
  4. 有了 pbd 之后,接下来 new 一个 RootBeanDefinition,然后调用 overrideFrom 方法进行属性合并,合并的方式就是用传入的 BeanDefinition 中的属性去覆盖 pbd 中同名的属性。
  5. 最后就是再设置 scope 属性等,然后把 mbd 返回即可。

核心流程就是上面这个步骤,如此之后,拿到手的就是和 parent 合并之后的 BeanDefinition 了。

3. 小结

最后我们再来稍微总结下:

使用 parentName 属性的一个主要优势是提高代码的可维护性和重用性。当我们需要创建多个相似的 bean 时,可以通过定义一个基础 bean,并在其他 bean 中使用 parentName 属性来继承其配置。这样,我们只需在基础 bean 中定义一次配置,而不必为每个派生 bean 重复相同的配置。

另一个使用 parentName 属性的场景是在多个层次结构中定义 bean。假设我们有一个通用的基础服务层 bean,而不同的业务模块需要在此基础上进行扩展。通过使用 parentName 属性,我们可以为每个业务模块定义一个派生 bean,并在其中添加特定于模块的配置。这种层次结构的定义使得我们可以更好地组织和管理不同模块之间的 bean。

通过使用 parentName 属性,我们可以轻松地创建和管理 bean 的层次结构。这种继承关系使得我们可以更好地组织和重用 bean 的配置,减少了代码的冗余性。同时,它还提供了一种灵活的方式来定义不同模块之间的 bean,使得应用程序更易于扩展和维护。

综上所述,Spring 框架中的 BeanDefinition 的 parentName 属性允许我们在定义 bean 时建立父子关系,从而提高代码的可维护性和重用性。通过继承已有 bean 的配置,我们可以避免重复编写相似的配置,并更好地组织和管理不同层次结构的 bean。

有的小伙伴们可能会搞混今天内容和之前松哥所写的 Spring 父子容器之间的关系,小伙伴们参考这篇文章就清楚啦:Spring 中的父子容器是咋回事?。

另外,Spring BeanDefinition 中的 parentName 和 Java 中的继承虽然有点像,但是并不能同等看待,它们之间也还是有一些区别的:

  1. 概念和作用:Java 中的继承是一种面向对象的编程概念,用于定义类之间的父子关系,子类继承父类的属性和方法。而在 Spring 中,BeanDefinition 的 parentName 属性是用于定义 bean 之间的父子关系,一个派生 bean 可以继承另一个已定义的 bean 的配置。
  2. 语法和用法:在 Java 中,继承是通过使用关键字 extends 来实现的,子类通过继承父类来获得父类的属性和方法。而在 Spring 中,通过在 BeanDefinition 中配置 parentName 属性来指定一个 bean 的父 bean,从而继承父 bean 的配置。
  3. 范围和应用:Java 中的继承主要用于类的继承关系,用于定义类之间的层次结构和代码的重用。而在 Spring 中,BeanDefinition 的继承主要用于定义 bean 之间的配置继承关系,用于组织和管理 bean 的配置,提高代码的可维护性和重用性。

好啦,Spring BeanDefinition 中的 parentName 属性现在大家明白了吧~

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

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

相关文章

数字化智慧工地云平台,劳务实名制系统、视频监控系统、环境监测系统、人员定位系统、工资代发系统、AI识别系统、视频监控系统

智慧工地概念 智慧工地是一种崭新的工程全生命周期管理理念&#xff0c;是指运用信息化手段&#xff0c;通过对工程项目进行精确设计和施工模拟&#xff0c;围绕施工过程管理&#xff0c;建立互联协同、智能生产、科学管理的施工项目信息化生态圈&#xff0c;并将此数据在虚拟…

RabbitMQ的5种消息队列

RabbitMQ的5种消息队列 1、七种模式介绍与应用场景 1.1 简单模式(Hello World) 一个生产者对应一个消费者&#xff0c;RabbitMQ 相当于一个消息代理&#xff0c;负责将 A 的消息转发给 B。 应用场景&#xff1a;将发送的电子邮件放到消息队列&#xff0c;然后邮件服务在队列…

LeetCode150道面试经典题-- 环形链表(简单)

1.题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&…

通过pycharm使用git和github的步骤(图文详解)

一、在Pycharm工具中配置集成Git和GitHub。 1.集成Git。 打开Pycharm, 点击File-->Settins-->Version Control-->Git 然后在 Path to Git executable中选择本地的git.exe路径。如下图&#xff1a; 2.集成GitHub 打开Pycharm, 点击File-->Settins-->Version…

基于Googlenet深度学习网络的信号调制类型识别matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 深度学习与卷积神经网络 4.2 数据预处理 4.3 GoogLeNet结构 4.4 分类器 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ............…

Qt 父子对象的关系

文章目录 前言一. Qt 对象可以存在父子关系&#xff1a;二. 父子关系的建立&#xff1a;三. Qt 对象的销毁&#xff1a; 总结 前言 Qt是一个流行的C框架&#xff0c;用于开发跨平台的图形用户界面&#xff08;GUI&#xff09;应用程序。Qt提供了一种强大的对象模型&#xff0c…

leetcode做题笔记83删除排序链表中的重复元素

给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 思路一&#xff1a;模拟题意 struct ListNode* deleteDuplicates(struct ListNode* head){i…

韦东山-电子量产工具项目:显示单元

所有代码都已通过测试跑通&#xff0c;其中代码结构如下&#xff1a; 一、include文件夹 1.1 disp_manager.h #ifndef _DISP_MANAGER_H //防止头文件重复包含,只要右边的出现过&#xff0c;就不会再往下编译 #define _DISP_MANAGER_H //区域结构体 typedef struct DispBuff …

机器学习样本数据划分的典型Python方法

机器学习样本数据划分的典型Python方法 DateAuthorVersionNote2023.08.16Dog TaoV1.0完成文档撰写。 文章目录 机器学习样本数据划分的典型Python方法样本数据的分类Training DataValidation DataTest Data numpy.ndarray类型数据直接划分交叉验证基于KFold基于RepeatedKFold基…

【C++】面向对象编程引入 ( 面向过程编程 | 查看 iostream 依赖 | 面向对象编程 )

文章目录 一、面向过程编程二、查看 iostream 依赖三、面向对象编程 一、面向过程编程 给定 圆 的 半径 , 求该圆 的 周长 和 面积 ; 半径为 r r r , 周长就是 2 π r 2 \pi r 2πr , 面积是 π r 2 \pi r^2 πr2 ; 使用 面向过程 的方法解决上述问题 , 只能是令程序顺序执…

DDCX——运维开发准备

DD——运维开发准备 一4 linux用的什么版本&#xff0c;常见命令&#xff08;awk sed grep telnet netstate tcpdump top ps perf&#xff09;5 数据库有哪些类型&#xff0c;关系型数据库有哪些&#xff0c;非关系型数据库有哪些6 mysql事务7 mysql集群了解多少8 redis数据类型…

Spring 框架入门介绍及IoC的三种注入方式

目录 一、Spring 简介 1. 简介 2. spring 的核心模块 ⭐ 二、IoC 的概念 2.1 IoC 详解 2.2 IoC的好处 2.3 谈谈你对IoC的理解 三、IoC的三种注入方式 3.1 构造方法注入 3.2 setter方法注入 3.3 接口注入&#xff08;自动分配&#xff09; 3.4 spring上下文与tomcat整…

ChatGPT聊天微信小程序源码/适配H5和WEB端

ChatGPT-MP(基于ChatGPT实现的微信小程序&#xff0c;适配H5和WEB端) 可二开包含前后台&#xff0c;支持打字效果输出流式输出&#xff0c;支持AI聊天次数限制&#xff0c;支持分享增加次数等功能。开源版禁止商用&#xff0c;仅供学习交流&#xff0c;禁止倒卖。 技术栈&…

本地linux 搭建云服务器

本人穷逼&#xff0c;三年268 腾讯云可以接受&#xff0c;续费千百块 承担不起 研究了一会&#xff0c;发现搭建云服务器有两种较好的方式 一种是有公网IP的&#xff0c;另外是没有公网IP的&#xff0c;这里实验成功的是没有公网ip的方法 这种方法有缺点&#xff0c;因为fre…

前端组件高级封装技巧--纯干货

对于前端的小伙伴来说&#xff0c;最常见的工作就是写后台管理系统的页面&#xff0c;而后台管理系统最多的操作就是CRUD了&#xff0c;类似下面的&#xff0c;一个搜索框&#xff0c;一个表格&#xff0c;一个分页&#xff0c;然后点击新增编辑有个弹框 当你写过一段时间的CRU…

OpenDDS安装教程 Java开发

一、环境搭建 1、版本介绍 笔者使用以下版本&#xff08;不同版本的openDDS对应ACETAO版本不同&#xff09; openDDS&#xff1a;3.14 ACETAO&#xff1a;6.5.12 perl&#xff1a;5.32.0.1-64bit Visual Studio&#xff1a;Community 2019 jdk&#xff1a;jdk-8u111-windows-…

spark的standalone 分布式搭建

一、环境准备 集群环境hadoop11&#xff0c;hadoop12 &#xff0c;hadoop13 安装 zookeeper 和 HDFS 1、启动zookeeper -- 启动zookeeper(11,12,13都需要启动) xcall.sh zkServer.sh start -- 或者 zk.sh start -- xcall.sh 和zk.sh都是自己写的脚本-- 查看进程 jps -- 有…

【CTF-web】备份是个好习惯(查找备份文件、双写绕过、md5加密绕过)

题目链接&#xff1a;https://ctf.bugku.com/challenges/detail/id/83.html 经过扫描可以找到index.php.bak备份文件&#xff0c;下载下来后打开发现是index.php的原代码&#xff0c;如下图所示。 由代码可知我们要绕过md5加密&#xff0c;两数如果满足科学计数法的形式的话&a…

centos7离线安装gdal3.6.3

本文档以纯离线环境为基础&#xff0c;所有的安装包都是提前下载好的。以gdal3.6.3为例&#xff08;其他版本安装步骤或方式可能不同&#xff09;&#xff0c;在centos7系统离线安装&#xff0c;并运行java项目&#xff0c;实现在java服务中调用gdal库解析地理数据。以下任意组…

python的 __all__ 用法

一、介绍 在Python中&#xff0c;__all__通常用于定义模块的公开接口。在使用from module import *语句时&#xff0c;此时被导入模块若定义了__all__属性&#xff0c;则只有__all__内指定的属性、方法、类可被导入&#xff1b;若没定义&#xff0c;则导入模块内的所有公有属性…