深入源码分析Spring为什么不支持构造方法的循环依赖

2023-10-31

我们知道当通过构造方法的方式注入属性时,是不支持循环依赖这种场景的,本文主要通过分析源码看看为什么构造方法不能支持循环依赖。

当然,如果读者还不了解循环依赖的问题,建议先结合源码搞清楚,可以先看看这篇文章,
深入源码分析Spring如何解决循环依赖,否则很有可能看不懂本文内容。

先看问题

@Component
public class TestA {

    private final TestB testB;

    public TestA(TestB testB) {
        this.testB = testB;
    }

}
@Component
public class TestB {

    private final TestA testA;

    public TestB(TestA testA) {
        this.testA = testA;
    }
}

TestA和TestB通过构造方法互相注入依赖,结果启动报错咯!
在这里插入图片描述
进入源码分析

假设先准备实例化TestA对象,直接找到实例化关键代码部分。

				//省略部分源码。。。
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				//省略部分源码。。。
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				//这个方法发挥了重要的作用
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//调用createBean方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

beforeSingletonCreation,上编文章分析过,把当前bean放到一个set集合中,错误就是从这抛出来的,也就是放入set集合失败了,稍微我们再来分析,第一次肯定是没问题的。

	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

createBean方法

	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		//省略部分源码。。。
		try {
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		//省略部分源码。。。
	}

doCreateBean 这个是核心方法,本文就关注实例化这个方法,在非构造方法依赖的情况下,这个方法是通过反射调用无参构造方法实例化的,而现在不行了。

当Spring发现有autowiring的构造函数,就会通过这个构造函数进行实例化。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
			//省略部分源码。。。
			if (instanceWrapper == null) {
			//实例化
			instanceWrapper = createBeanInstance(beanName, mbd, args);
			//省略部分源码。。。
		}
}
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		
		//省略部分源码。。。
		
		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			//有参调用这个方法
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		//无参构造函数是调用这个方法
		return instantiateBean(beanName, mbd);
	}

autowireConstructor

	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
			//省略部分源码。。。
		argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
			//省略部分源码。。。
								
	}

createArgumentArray

	private ArgumentsHolder createArgumentArray(
			String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
			BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
			boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
		//省略部分源码。。。
		try {
					Object autowiredArgument = resolveAutowiredArgument(
							methodParam, beanName, autowiredBeanNames, converter, fallback);
					args.rawArguments[paramIndex] = autowiredArgument;
					args.arguments[paramIndex] = autowiredArgument;
					args.preparedArguments[paramIndex] = autowiredArgumentMarker;
					args.resolveNecessary = true;
				}
				catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(
							mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
				}
				//省略部分源码。。。
	}

resolveAutowiredArgument

	@Nullable
	protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
			@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
			//省略部分源码。。。
		try {
			return this.beanFactory.resolveDependency(
					new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
		}
		catch (NoUniqueBeanDefinitionException ex) {
			throw ex;
		}
			//省略部分源码。。。
	}

resolveDependency这个方法在 深入源码分析Spring如何解决循环依赖 文中有看过,最终会调用到beanName的getBean方法。

源码看到这边已经足够了,我们可以看出与无参构造函数不同之处在于,依赖属性的getBean调用提前了,无参构造函数是在类实例化完成,并且放入到了三级缓存中之后才调用的,而现在不是了,也就是说现在缓存中并没有TestA。

所以当调用getBean(testB)时,同样会走到getBean(testA),此时缓存中没有testA,那么就又会反复执行testA的实例化过程,那么是在什么地方终止的呢?

就是这个方法了,此时testA要想再加入这个set集合就会返回false了,因为第一次testA的实例化过程就已经放入这个set集合了,所以此处就抛出了异常,终止了整个流程。

	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

构造方式依赖注入
在这里插入图片描述
@Autowired属性依赖注入
在这里插入图片描述
总结

通过构造方法之所以失败的主要原因就是,依赖的属性被提前实例化了,因为testA未完成实例化,内存中也就不存在有关testA的任何记录,自然缓存中也没有,所以当再次触发testA的实例化时,也没法从缓存中获取,最终通过一个set集合判断是否被重复添加来控制,避免陷入死循环。。。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入源码分析Spring为什么不支持构造方法的循环依赖 的相关文章

随机推荐

  • python按钮居中_button按钮居中

    今天在写页面时 发现给button按钮设置居中时 css页面写了text align center 但是不起作用 用了display属性也无作用 试了好多次发现要给button按钮添加个div 然后让div居中就可以了 以下写个test来说
  • 掌握Python的X篇_36_定义类、名称空间

    本篇将会重新回到python语法的主线 并且开展新的篇章 那就是面向对象的编程 文章目录 1 面向对象 2 定义类 3 类的名称空间性质 1 面向对象 面向对象是一种编程的思想 并不是限制在某一种语言上的 不同语言面向对象的表达能力是不一样
  • OpenWRT 使用USB 4G上网卡

    笔者使用的是一个4G上网卡 可以通过USB插在电脑上进行上网 即可做网卡使用 我使用的是树莓派4B来安装OpenWrt 其他硬件大同小异 只要支持USB并且能驱动上网卡 笔者的硬件环境如下图 插入USB上网卡 在管理面板操作 网络 gt 接
  • 如何设置ntp出现漂移的问题

    如何设置ntp出现漂移的问题 1 执行date命令 查看该host机器时间是否正常 2 找到ntp服务器 执行ntpdate time ntp org 3 在crontab中添加 0 12 usr sbin ntpdate
  • c++_设计一个 Studnet(学生)类

    设计一个 Studnet 学生 类 1 基本信息 学号 姓名 性别 出生日期 年级 班级 院系 专业 其中 基本信息为 private 属性 成员函数为 public 属性 2 Student 类有多个构造函数 缺省构造函数 带参数的构造函
  • JUC基础——线程池

    juc基础 线程池 前言 一 线程池是什么 二 管理线程池 1 线程池种类 2 线程池参数 3 创建线程池 三 线程池状态 四 线程池的任务提交 1 execute 2 submit 五 线程执行异常 六 线程池执行步骤 简易 七 线程池执
  • 9个 强化学习现实生活中的应用

    大多数人类和动物的学习可以说属于无监督学习 有人说 如果智能是一块蛋糕 那么无监督学习就是蛋糕 监督学习是锦上添花 强化学习是锦上添花 这似乎很有趣 对吧 强化学习是最接近人类学习的 就像我们人类从我们生活的动态环境中学习 我们的行为决定我
  • java不通过构造函数创建对象(Unsafe)

    java中不通过构造函数创建对象 也有说不创建对象直接执行成员方法 这里就不和你们扯什么通过 反序列化 clone等方法了 个人觉得都是在胡扯 如何不执行构造函数创建对象 先来带大家认识一个类 sun misc Unsafe 该类主要提供一
  • MySQL入門_テーブル作成

    create database case a show databases drop database case a create database case db use case db drop table if exists user
  • JS之返回字符串最后出现的位置lastIndexOf

    作用 lastIndexOf 方法可返回一个指定的字符串值最后出现的位置 在一个字符串中的指定位置从后向前搜索 语法 stringObject lastIndexOf searchvalue fromindex 参数1 必需 规定需检索的字
  • [ 人力资源面试篇 ] HR 面试题分析详解大集合,看完直怼面试官(一)

    博主介绍 博主介绍 大家好 我是 PowerShell 很高兴认识大家 主攻领域 渗透领域 数据通信 通讯安全 web安全 面试分析 点赞 评论 收藏 养成习惯 一键三连 欢迎关注 一起学习 一起讨论 一起进步 文末有彩蛋 作者水平有限 欢
  • Python面试题

    1 一行代码实现1 100之和 利用sum 函数求和 2 如何在一个函数内部修改全局变量 函数内部global声明 修改全局变量 3 列出5个python标准库 os 提供了不少与操作系统相关联的函数 sys 通常用于命令行参数 re 正则
  • 社区发现:论文中模块度Q的计算

    2 Extending the definition of modularity to directed graphs with overlapping communities 参考文献 1 刘传建 复杂网络中的社团结构划分及分析应用 D
  • glfwPollEvents()程序崩溃

    系列文章目录 文章目录 系列文章目录 前言 一 程序崩溃的地方找不到 二 解决步骤 1 vs2019中打开 诊断工具 窗口 2 在vs2019中打开 并行堆栈 窗口 总结 例子 源码下载 前言 如果使用GLFW 没有这句glfwPollEv
  • dz 2级域名

    例如 http www cheungfei com 开启了二级域名 http bbs cheungfei com 开启之后发现不能同步登陆 刚开始以为要在Ucenter中添加应用 后来发现问题还没有解决 到DISCUZ官网看看别人的帖子 才
  • 多线程、定时器----基础认识篇1

    1 多线程一般通过使用thread的子类或者往thread构造方法中传入runnable对象实现 也就是常说的 继承 thread类 和 实现runnable接口 注 实现runnable接口可以实现线程 原理是 在thread的run方法
  • Instagram Shop如何开通?如何销售?最全面攻略

    借助 Instagram 商店 品牌可以策划一系列可购物的商品 这些商品可通过其 Instagram 个人资料直接访问 这使得在应用程序上销售更容易 也被潜在客户发现 一 什么是Instagram Shop Instagram 商店为商家提
  • 2021 年山东省职业院校技能大赛中职组“网络安全” 赛项

    2021 年山东省职业院校技能大赛 中职组 网络安全 赛项 竞赛题库 网络安全赛项专家组 2021 年 11 月 赛题说明 一 竞赛时间安排与分值权重 二 竞赛拓扑图 模块编号 模块名称 竞赛时间 小时 权值 A 基础设施设置与安全加固 3
  • php 操作 MySQL 中的Blob类型

    我们需要保存一个文件或者一张图片或者其他二进制或多媒体文件时 经常选择使用 类型 一 基本使用 Blob就是一种Mysql的数据类型 它是一个二进制大型对象 可以作为大量数据的容器 其实更准确地说Blob是一系列数据类型 MySQL的四种B
  • 深入源码分析Spring为什么不支持构造方法的循环依赖

    我们知道当通过构造方法的方式注入属性时 是不支持循环依赖这种场景的 本文主要通过分析源码看看为什么构造方法不能支持循环依赖 当然 如果读者还不了解循环依赖的问题 建议先结合源码搞清楚 可以先看看这篇文章 深入源码分析Spring如何解决循环