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<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存

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

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> 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<?> 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?
	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?
	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?
	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<String> beanNameSet = new HashSet<>(2);
		beanNameSet.add((String) curVal);
		beanNameSet.add(beanName);
		this.prototypesCurrentlyInCreation.set(beanNameSet);
	}
	else {
		Set<String> beanNameSet = (Set<String>) curVal;
		beanNameSet.add(beanName);
	}
}

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

循环依赖开关

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



二级缓存可行?

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

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

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

前言

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

(0)

相关推荐

  • 详解Spring循环依赖的解决方案

    spring针对Bean之间的循环依赖,有自己的处理方案.关键点就是三级缓存.当然这种方案不能解决所有的问题,他只能解决Bean单例模式下非构造函数的循环依赖. 我们就从A->B->C-A这个初始化顺序,也就是A的Bean中需要B的实例,B的Bean中需要C的实例,C的Bean中需要A的实例,当然这种需要不是构造函数那种依赖.前提条件有了,我们就可以开始了.毫无疑问,我们会先初始化A.初始化的方法是org.springframework.beans.factory.support.Abstra

  • Spring中循环依赖的解决方法详析

    前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入. 一.先说说Spring解决的循环依赖是什么 Java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖.

  • 详解Spring-bean的循环依赖以及解决方式

    本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可以应用在我们实际开发项目中. 1. 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的相互依赖关系.循环调用其实就是一个死循环,除非有终结条件. Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖. 循环依赖的产生和解

  • Spring为何要用三级缓存来解决循环依赖问题

    我们都知道Spring为了解决循环依赖使用了三级缓存 Spring三级缓存 一级缓存singletonObjects 用于保存BeanName和创建bean实例之间的关系,beanName -> bean instance private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); 二级缓存earlySingletonObjects 保存提前曝光的单例bean对象 private fin

  • 浅谈Spring如何解决循环依赖的问题

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘.本文主要针对这个问题,从源码的角度对其实现原理进行讲解. 1. 过程演示 关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化.在Spring中,对象的实例化是通过反射实

  • Spring循环依赖的解决办法,你真的懂了吗

    介绍 先说一下什么是循环依赖,循坏依赖即循环引用,两个或多个bean相互引用,最终形成一个环.Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成 Spring的循环依赖有两种场景 构造器的循环依赖 属性的循环依赖 构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载.在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入 属性的循环依赖主要是通过3个map来解决的 构造器的循环依赖 @Component publi

  • 基于SpringBoot构造器注入循环依赖及解决方式

    1. 循环依赖是什么? Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖. Bean A → Bean B → Bean A 更复杂的间接依赖造成的循环依赖如下. Bean A → Bean B → Bean C → Bean D → Bean E → Bean A 2. 循环依赖会产生什么结果? 当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean. 例如,有如下依赖: Bean A → Bean B → Bean C Spring

  • 浅谈Spring 解决循环依赖必须要三级缓存吗

    我们都知道 Spring 是通过三级缓存来解决循环依赖的,但是解决循环依赖真的需要使用到三级缓冲吗?只使用两级缓存是否可以呢?本篇文章就 Spring 是如何使用三级缓存解决循环依赖作为引子,验证两级缓存是否可以解决循环依赖. 循环依赖 既然要解决循环依赖,那么就要知道循环依赖是什么.如下图所示: 通过上图,我们可以看出: A 依赖于 B B 依赖于 C C 依赖于 A public class A { private B b; } public class B { private C c; }

  • 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖. 如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建Test

  • 详解Spring Bean的循环依赖解决方案

    如果使用构造函数注入,则可能会创建一个无法解析的循环依赖场景. 什么是循环依赖 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的相互依赖关系.循环调用其实就是一个死循环,除非有终结条件. Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖. 怎么检测是否存在循环依赖 检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,

随机推荐