循环依赖就是不同的对象之间存在互相依赖的关系。比如说类A的构造器依赖B实例,B的构造器依赖A实例,那么Spring在初始化A实例时需要注入B实例,那么就需要从容器中拿B实例,这时就会去创建B实例,但是创建B实例又需要依赖A实例,。。。这时就出现了循环依赖的情况。(或者是多中对象之间的循环依赖 A依赖B | B依赖C | C依赖A等)
1.2代码举例普通类A/B
public class A {
private A a;
public A(B b) {
this.a = a;
}
}
public class B {
private A a;
public B(A a){
this.a = a;
}
}
Spring配置文件
测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
运行结果
报错
//Spring检测出了循环依赖。 Is there an unresolvable circular reference2.Spring存在哪几种循环依赖
Spring存在三种循环依赖
1.原型循环依赖
即原型模式对象A(即scope = prototype的bean)循环依赖了单例或者原型模式的的B
这种循环依赖Spring无法解决,只能抛出异常。
2.单例构造方法循环依赖
即1.2的代码示例,Spring无法解决,只能抛出异常
3.单例set注入循环依赖
Spring可以解决,通过三级缓存(下面详细讲解)提前暴露一个早期对象解决.
3.Spring源码层面如何检查出循环依赖图示
3.1单例构造方法注入循环依赖检测通过一个Set集合,里面记录的是当前正在创建**(未完成创建和初始化)**的beanName。
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) &&
!this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
// singletonsCurrentlyInCreation 是一个Set集合
private final Set singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
bean完成创建和所有的初始化后,对应的beanName就会从set集合中移除
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) &&
//将创建成功的beanName从set集合中删除
!this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
3.2原型模式循环依赖检测
图示
这里的检测和3.1单例构造方法注入类似,不过这里使用的是一个ThreadLocal集合,里面存储了当前正在创建的beanName。
try {
//记录当前线程正在创建的原型对象的beanName
beforePrototypeCreation(beanName);
//创建对象
prototypeInstance = createBean(beanName, mbd, args);
} finally {
//从正在创建中的集合中(HashSet)移除
afterPrototypeCreation(beanName);
}
beforePrototypeCreation
protected void beforePrototypeCreation(String beanName) {
//获取ThreadLocal内部的对象
Object curVal = this.prototypesCurrentlyInCreation.get();
//curVal == null,说明内部的value还没有初始化,直接存一个String即可
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
//curVal有值 是String,此时还需要继续添加当前beanName,构造一个Set集合
} else if (curVal instanceof String) {
Set beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
//curVal已经是Set集合了,直接add即可
} else {
Set beanNameSet = (Set) curVal;
beanNameSet.add(beanName);
}
}
afterPrototypeCreation
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
//curVal是一个String,直接调用ThreadLocal的remove()方法
//将当前线程关联的ThreadLocal干掉。
this.prototypesCurrentlyInCreation.remove();
} else if (curVal instanceof Set) {
Set beanNameSet = (Set) curVal;
//移除当前beanName
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
原型循环依赖的检测过程
在getBean()方法执行的一个环节中**(先于创建单实例和原型实例的前面)**,会执行下面的代码,判断当前是否产生了原型循环依赖
//返回true,表示当前beanName对应的bean正在创建中,说明发生了循环依赖直接抛异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
isPrototypeCurrentlyInCreation
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
//获取ThreadLoacl内部的String或者Set集合
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set>) curVal).contains(beanName))));
}
3.3总结
Spring检测循环依赖的核心就是使用一个Set集合存储当前正在创建的bean的beanName,假设A和B循环依赖,创建A实例时发现依赖B,将beanNameA存到Set集合中,然后执行getBean(B)的流程,也将beanNameB存到Set集合中,然后B依赖A走到getBean(A)的流程,但是发现Set集合中已经存在了beanNameA,那么说明发生了循环依赖,直接抛出异常。
4.Spring如何解决单例Set注入的循环依赖 4.1Spring的三级缓存Spring解决单例Set注入循环依赖的核心就是使用三级缓存将创建出来的早期对象 (未完成后续注入和初始化) 包装成一个ObjectFactory放到三级缓存中去。
private final Map4.2源码-创建单例bean时放入三级缓存singletonObjects = new ConcurrentHashMap<>(256); private final Map > singletonFactories = new HashMap<>(16); private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);
//此时的bean是刚刚通过反射创建的一个早期对象,还未完成注入和初始化后处理器等操作。
final Object bean = instanceWrapper.getWrappedInstance();
····
// 是否需要提早曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
//打印日志
}
// 为避免后期循环依赖,可以在bean初始化完成之前将创建实例的ObjectFactory加入工厂
addSingletonFactory(beanName,
() -> getEarlyBeanReference(beanName, mbd, bean));
}
//----------------------------------------------------------------------------
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);
}
}
}
4.3源码-getBean()时尝试先从缓存中获取
Object sharedInstance = getSingleton(beanName);
getSingleton()
就是在进行创建对象前尝试先从缓存中获取,此时如果是单例set注入循环依赖,此时会从三级缓存中拿到早期对象,完成注入,然后走完整个创建流程,这样就解决了循环依赖。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从一级缓存中拿
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
//二级缓存也没有 去三级缓存中查看。
if (singletonObject == null && allowEarlyReference) {
//加锁
synchronized (this.singletonObjects) {
//尝试继续从一级缓存中获取
singletonObject = this.singletonObjects.get(beanName);
//一级缓存中不存在
if (singletonObject == null) {
//再次尝试从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
//二级缓存也不存在
if (singletonObject == null) {
//从三级缓存中获取
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
//三级缓存不为NULL
if (singletonFactory != null) {
//获取包装的ObjectFactory中的早期对象
singletonObject = singletonFactory.getObject();
//将早期对象放到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//将这个beanName对应的ObjectFactory从三级缓存中干掉
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
4.4总结
这也可以看出原型模式循环依赖和单例构造方法循环依赖为什么无法解决了。
- 1.原型模式创建的早期对象不会放到三级缓存中去,所以无法通过上面的方法解决。
- 2.单例构造方法循环依赖是在还没有将早期对象创建出来就发生循环依赖了,即早期对象根本就创建不出来,也不能通过上面的方式解决。



