热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

spring如何快速稳定解决循环依赖问题

这篇文章主要介绍了spring如何快速稳定解决循环依赖问题,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B依赖于C,C又依赖于A。其实一个bean持有自己类型的属性也会产生循环依赖。

setter singleton循环依赖

使用

SingleSetterBeanA依赖SingleSetterBeanB,SingleSetterBeanB依赖SingleSetterBeanA。

@Data
public class SingleSetterBeanA {
	@Autowired
	private SingleSetterBeanB singleSetterBeanB;
}
@Data
public class SingleSetterBeanB {
	@Autowired
	private SingleSetterBeanA singleSetterBeanA;
}

源码分析

spring是通过三级缓存来解决循环依赖的,那么三级缓存是怎么工作的呢?

三级缓存对应org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类的三个属性:

/** Cache of singleton objects: bean name to bean instance. */
private final Map singletOnObjects= new ConcurrentHashMap<>(256); // 一级缓存

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletOnFactories= new HashMap<>(16); // 二级缓存

/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletOnObjects= new ConcurrentHashMap<>(16); // 三级缓存

在这里插入图片描述

对于setter注入造成的依赖是通过Spring容器提前暴露刚完成实例化但未完成初始化的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,关键源码如下所示:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// 处理循环依赖,实例化后放入三级缓存
boolean earlySingletOnExposure= (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

bean实例化后放入三级缓存中:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<&#63;> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			this.singletonFactories.put(beanName, singletonFactory); // 三级缓存
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

放入三级缓存中的是ObjectFactory类型的lambda表达式:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				/**
				 * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference(java.lang.Object, java.lang.String)
				 */
				// 使用AbstractAutoProxyCreator#getEarlyBeanReference创建代理对象
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

构造器参数循环依赖

通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

使用

@Data
public class SingleConstrutorBeanA {
	public SingleConstrutorBeanA(SingleConstrutorBeanB singleConstrutorBeanB) {
	}
}
@Data
public class SingleConstrutorBeanB {
	public SingleConstrutorBeanB(SingleConstrutorBeanA singleConstrutorBeanA) {
	}
}

上面的代码运行时会抛出如下异常:

... ...
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'singleConstrutorBeanB': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference&#63;
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:805)
 at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1403)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1245)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:538)
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:329)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
 at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)
 at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892)
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796)
 ... 76 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference&#63;
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
 at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)
 at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892)
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796)
 ... 90 more

源码分析

Spring容器会将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

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

在这里插入图片描述

@Lazy打破循环依赖

在上面的例子中只需要在SingleConstrutorBeanA或者SingleConstrutorBeanB的构造方法上面加上@Lazy注解,就会发现不会抛出异常了,这又是为什么呢?

下面假设在SingleConstrutorBeanA的构造方法上面加了@Lazy注解,在构造B时,发现参数A时被@Lazy注解修饰时,那么就不会调用getBean来获取对象,而是创建了一个代理对象,所以不会构成真正的循环依赖,不会抛出BeanCurrentlyInCreationException异常。

/**
 * 处理懒加载对象
 * 懒加载返回的又是一个代理对象,不会真正的调用getBean,所以如果构造方法依赖中有循环依赖,那么不会报错
 * @see org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String)
 */
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
		descriptor, requestingBeanName);
if (result == null) {
	// 调用beanFactory.getBean(beanName)从容器中获取依赖对象
	result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;

在这里插入图片描述

setter prototype循环依赖

对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存"prototype"作用域的bean,因此无法提前暴露一个创建中的bean。

使用

@Data
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBeanA {
	@Autowired
	private PrototypeBeanB prototypeBeanB;
}
@Data
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBeanB {
	@Autowired
	private PrototypeBeanA prototypeBeanA;
}
@Test
public void test3() {
	AnnotationConfigApplicationContext applicatiOnContext= new AnnotationConfigApplicationContext();
	applicationContext.register(PrototypeBeanA.class);
	applicationContext.register(PrototypeBeanB.class);
	applicationContext.refresh();
	applicationContext.getBean(PrototypeBeanA.class); // 此时必须要获取Spring管理的实例,因为现在scope="prototype" 只有请求获取的时候才会实例化对象
}

运行结果如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference&#63;
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:269)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
 at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1322)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:668)
 ... 89 more

源码分析

在这里插入图片描述

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

... ...
// 判断是否在当前创建Bean池中
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}
... ...

异常就是在上面的代码中抛出来的,那么beanName是什么时候添加至当前创建Bean池中的呢?

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	// prototype类型的bean的实例化
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation

protected void beforePrototypeCreation(String beanName) {
	// ThreadLocal
	Object curVal = this.prototypesCurrentlyInCreation.get();
	if (curVal == null) {
		this.prototypesCurrentlyInCreation.set(beanName);
	}
	else if (curVal instanceof String) {
		Set beanNameSet = new HashSet<>(2);
		beanNameSet.add((String) curVal);
		beanNameSet.add(beanName);
		this.prototypesCurrentlyInCreation.set(beanNameSet);
	}
	else {
		Set beanNameSet = (Set) curVal;
		beanNameSet.add(beanName);
	}
}

其根本原因就是Spring容器不会对prototype类型的bean进行缓存,因此无法提前利用三级缓存暴露一个代理对象。

循环依赖开关

可以通过allowCircularReferences来禁止循环依赖,这样的话,singleton bean的setter循环依赖也会报错。

applicationContext.setAllowCircularReferences(false);

二级缓存可行?

缓存 说明
singletonObjects 第一级缓存,存放可用的成品Bean。
earlySingletonObjects 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化,用以解决循环依赖。
singletonFactories 第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中,用以解决循环依赖。

理论上二级缓存时可行的,只需要将三级缓存中BeanFactory创建的对象提前放入二级缓存中,这样三级缓存就可以移除了。

那么spring中为什么还要使用三级缓存呢?如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

到此这篇关于spring如何快速稳定解决循环依赖问题的文章就介绍到这了,更多相关spring循环依赖内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
    .NETCore中的一个接口多种实现的依赖注入与动态选择看这篇就够了最近有个需求就是一个抽象仓储层接口方法需要SqlServer以及Oracle两种实现方式,为了灵活我在依赖注入的 ... [详细]
  • 本文详细介绍了如何在Spring Boot项目中配置Maven的pom.xml文件,包括项目的基本信息、依赖管理及构建插件的设置。 ... [详细]
  • JSP服务器概述及搭建指南
    本文详细介绍了JSP服务器的概念、主流服务器软件及其搭建步骤,旨在帮助开发者更好地理解和使用JSP技术。 ... [详细]
  • Docker入门与实践指南
    本文介绍了Docker的基础知识,包括其作为开源应用容器引擎的特点,以及如何利用Docker将应用程序及其依赖项打包成轻量级的容器镜像。同时,还详细讲解了Docker的核心概念、安装过程及基本命令操作。 ... [详细]
  • FFPlay 字幕与LRC歌词播放指南
    本文详细介绍了不同媒体容器支持的字幕格式,以及如何使用FFPlay和FFMPEG进行字幕和LRC歌词的播放与转换。涵盖的内容包括字幕显示方法、字体配置、字幕流选择等。 ... [详细]
  • 构建首个Spring MVC应用程序
    本指南将指导您如何从零开始创建一个简单的Spring MVC应用,涵盖项目模块创建、依赖管理、核心配置及控制器开发等关键步骤。 ... [详细]
  • 本文探讨了亚马逊Go如何通过技术创新推动零售业的发展,以及面临的市场和隐私挑战。同时,介绍了亚马逊最新的‘刷手支付’技术及其潜在影响。 ... [详细]
  • 如何打造属于自己程序的菜单栏,以上代码清晰的展示了swing是如何创建菜单栏的。只要理清楚javaswing的容器和面板的逻辑顺序就能掌握swing ... [详细]
  • 开发笔记:Python:GUI之tkinter学习笔记1控件的介绍及使用
    开发笔记:Python:GUI之tkinter学习笔记1控件的介绍及使用 ... [详细]
  • 本文探讨了几款Lua调试工具的特点和适用场景,特别是针对Lua 5.1和5.2版本。其中,Visual Studio插件因其高效性和强大的功能成为调试Lua 5.1的最佳选择。 ... [详细]
  • 本文通过一个简单的 C++ 示例,深入分析了当使用 `vector::resize` 方法调整向量大小时,对象的构造函数和析构函数被调用的具体情况。示例代码展示了如何创建一个包含自定义类的对象的向量,并通过调整其大小来观察构造和析构的过程。 ... [详细]
  • ECharts 基础使用指南
    本文档提供了一个简单的 ECharts 使用示例,帮助初学者快速了解如何在网页中集成和使用 ECharts 创建图表。更多详细信息请参阅官方文档:https://www.echartsjs.com/zh/tutorial.html#5%20分钟上手%20ECharts ... [详细]
  • 探讨了当类没有默认构造函数时,如何使用特定参数创建多个对象的方法。本文提供了多种解决方案,包括使用指针数组和标准库容器。 ... [详细]
  • 深入解析晶体振荡器的工作机制及其应用
    本文详细介绍了晶体振荡器的工作原理及主要功能,探讨了其在电子设备中的重要作用,并解释了相关技术术语,旨在为读者提供全面的技术参考。 ... [详细]
  • 企业级 Java 应用的关键性能指标解析
    本文探讨了衡量企业级 Java 应用性能的四大核心指标:商业事务、外部服务、垃圾回收及应用布局。这些指标不仅直接影响用户体验,还关系到系统的稳定性和效率。 ... [详细]
author-avatar
醒葛
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有