什么是循环依赖?
- A依赖B的同时,B也依赖了A,就构成了循环依赖。
@Component
public class A{
// 依赖B
@Autowired
private B b;
public B getB() {
return b;
}
}
@Component
public class B {
// 依赖A
@Autowired
private A a;
public A getA() {
return a;
}
}
//比较特殊的循环依赖
@Component
public class A{
// 依赖B
@Autowired
private A a;
}
在Spring官网中有提到Spring循环依赖的问题,Spring不推荐使用构造器的方式进行注入。(因为通过构造器注入是无法解决循环依赖的)而是通过setter方式注入,这样可以很好避免循环依赖的问题。
Spring的循环依赖过程跟bean的生命周期密切相关。实例化A的过程:
- 实例化A -> 填充属性B -> 发现B没有被实例化 -> 实例化B -> 填充属性A -> 发现A也没有被实例化 -> 最终失败。
那么,到底有没有办法解决这样的问题呢?
- 有的。核心在于:提前暴露这个对象的引用。(先暴露引用,后完成属性的填充)
最后,流程就变成了:
- 实例化A -> 暴露a对象 -> 填充属性B -> 发现B没有被实例化 -> 实例化B -> 填充属性A -> 实例化B成功> 实例化A -> 成功。
Spring的确就是这么做的。
想一想:为什么可以先提前暴露出a对象?
- 在java中,对象使用的是引用传递。即使提前暴露了,其实是不影响的。最终属性填充完后,最终结果都是正常的。
Spring通过三级缓存解决依赖注入
spring的思路和上述思路是一致的,并且Spring更加严谨!通过三级缓存来解决循环依赖的问题,把提前暴露的对象存放到三级缓存中,二级缓存存放的是过渡的bean,一级缓存存放的是最终形态的bean。

- 一级缓存(也叫单例池):存放已经经历了完整生命周期的Bean对象。
- 二级缓存:主要存放过渡bean,也就是三级缓存中ObjectFactory产生的对象。主要作用是防止bean被AOP切面代理时,重复通过三级缓存对象ObjectFactory创建对象。被代理情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。这明显不满足spring单例的原则,所以需要二级缓存进行缓存。
同时需要注意:二级缓存中存放的bean也是半成品的,此时属性未填充。
- 三级缓存:存放的对象为ObjectFactory类型,主要作用是产生bean对象。调用getObject()最终调用的是getEarlyBeanReference()。该方法的主要作用是:如果有需要,产生代理对象。如果bean被AOP切面代理,返回代理bean对象;如果未被代理,就返回原始的bean对象。
getEarlyBeanReference()调用的SmartInstantiationAwareBeanPostProcessor,其实是Spring留得拓展点,本质是通过BeanPostProcessor定制bean的产
生过程。绝大多数AOP(比如@Transactional)单例对象的产生,都是在这里进行了拓展,进而实现单例对象的生成。
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次都会从容器中获取一个新的对象,都是重新创建的,所以非单例的bean
是没有缓存的,不会放入到三级缓存中。
实例化A的过程:

实例化A的时候,需要注入B。因为Spring默认bean是单例的,因此会先去Spring容器中查找B。有的话直接注入,没有再进行创建。
此时是没有B的,因此需要先实例化B。

有意思的是实例化B的时候,发现需要注入a。因此,还是先在Spring容器中查找a。此时a是处于创建状态(提前暴露在三级缓存中),所以就直接获取了。注意:三级缓存中存放的并不是真正的bean对象,而是生成bean的ObjectFactory。到三级缓存获取的时候,会到AbstractAutowireCapableBeanFactory#getEarlyBeanReference()的处理,才能获取到bean,然后放入二级缓存中,同时返回a进行依赖注入。

通过提前暴露对象到多级缓存,已经成功将实例b中的属性a注入了,那后面的流程自然一路畅通:继续执行b的实例化initializeBean() -> 将b从正在创建列表移出-> 将b放入一级缓存(同时将b在二级缓存和三级缓存中删除) -> 返回b。

既然b实例化完了,那么又到回a了。后续的流程和b的后续流程一致。这里就不再赘述了。
其实这就是Spring解决循环依赖的流程,其核心思路就是:**先将bean提前暴露到三级缓存中,后续有依赖注入的话,先将这个半成品的bean进行注入。**之所以说这个bean是半成品,是因为暴露在三级缓存和二级缓存中的bean虽然已经创建成功,但是属性还没有进行填充,Aware回调等流程也没有执行,所以说它是一个不完整的bean对象。
为什么需要三级缓存?二级缓存行不行?
先说结论:不行!
在没有AOP的情况下,确实可以只通过一级缓存和三级缓存来解决循环依赖。回忆一下流程:
- 首先实例化A,实例化前先将半成品暴露在三级缓存中。
- 填充属性B,发现B还没有实例化,先去实例化B。
- 实例化B的过程中,需要填充属性A,从三级缓存中通过ObjectFactory#getObject()直接获取A(在没有AOP的场景下,多次获取的是同一个bean),进行依赖注入,并完成实例化流程。
- 获取到b,实例化A的流程继续,注入到b到a中,进而完成a的实例化。
但是如果bean被AOP代理了,那么就不一样了。最核心的区别点:就是每次调用ObjectFactory#getObject()都会产生一个新的代理对象(这一点是重中之重),我们用存在事务的场景测试一下:
@Component
public class A {
@Autowired
private B b;
@Transactional //增加事务注解,会对bean生成代理对象
public B getB() {
System.out.println(Thread.currentThread().getName());
return b;
}
}
此时A就会被AOP代理,生成的实例a也是代理对象了。我们debug验证一下,就会发现此时A确实被CGLIB代理了:

当我们多次调用ObjectFactory#getObject(),产生的代理对象不是同一个:

确实不是同一个。
当有AOP时,bean的创建示意图如下:

类A有AOP,A会把自己的引用提前暴露在缓存中,类B可以直接使用。但是当A实例化完,需要放入到一级缓存的时候,最终存放的确实代理对象。也就是说B拿到的是A的原始对象,但是一级缓存中存放的确实A的代理对象。那么就破坏了单例的规则了。
- 出现这个问题主要是上文提到过的 AOP 是通过 BeanPostProcessor 实现的,而 BeanPostProcessor 是在 属性注入阶段后 才执行的,所以会导致注入的对象有可能和最终的对象不一致。
那么问题来了:A是单例的,也就是要保证,在Spring中,使用到该bean的地方,都是同一个bean才行。但是每次执行singletonFactory.getObject()都会产生新的代理对象。假设只有一级和三级缓存,每次从三级缓存中获取代理对象,都会产生新的代理对象,忽略性能不说,是不符合单例原则的。
所以这里我们要借助二级缓存来解决这个问题,将singleFactory.getObject()产生的对象放到二级缓存中去,后面直接从二级缓存中拿,保证始终只有一个代理对象。

不支持循环依赖的情况
- 非单例的bean。
- constructor注入的bean。
- @Async方式的bean。
非单例的bean无法支持循环依赖
//AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 省略部分代码
// 是否支持循环依赖
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");
}
// 做循环依赖的支持 将早期实例化bean的ObjectFactory,添加到单例工厂(三级缓存)中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
}
解决循环依赖的前提:把半成品bean暴露到三级缓存中。
前置条件就是:
- mbd.isSingleton():要求bean是单例的。
- this.allowCircularReferences:是否允许循环依赖,默认为true,即默认支持循环依赖。
- isSingletonCurrentlyInCreation(beanName):判断当前bean是否正在创建中,默认是成立的,因为在创建bean的时候,会先设置该标志。
constructor注入导致无法支持循环依赖
@Component
public class A{
// 依赖B
private B b;
public A(B b){
this.b = b;
}
public B getB() {
return b;
}
}
@Component
public class B {
// 依赖A
private A a;
public A(A a){
this.a = a;
}
public A getA() {
return a;
}
}
A实例创建时 -> 构造注入B -> 查找B,容器中不存在,先实例化创建B -> 构造注入A -> 容器中不存在A(此时A还没有添加到三级缓存中) -> 异常UnsatisfiedDependencyException。因为暴露对象放入三级缓存的过程在实例创建之后,通过构造方法注入时,还没有放入三级缓存呢所以无法支持构造器注入类型的循环依赖。
通过上述的知识,我们了解了Spring解决循环依赖的大致流程。接下来,我们深入细节,感受Spring的强大魅力!
Spring是如何通过三级缓存解决AOP的问题呢?
我们已经知道了,在三级缓存中存放的是ObjectFactory。如果使用了AOP,那么往二级缓存存放的是代理的bean对象。如果没有使用AOP,存放的就是实例的bean对象。
那么ObjectFactory到底是什么呢?
深入源码doCreateBean()中的addSingletonFactory()
this.(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
可以看到addSingletonFactory()的第二个参数就是ObjectFactory。而ObjectFactory,它是通过匿名内部类来生成的。
进入getEarlyBeanReference(),点击进入getEarlyBeanReference(),发现它是一个接口,进入具体的实现。
可以看到方法中的第二个参数就是 ObjectFactory 类型,并且将其添加进 三级缓存(singletonFactories) 中。
也就是说 Spring 在加入缓存时,会将 实例化后生成的原始对象 通过 lambda 表达式调用 getObject() 方法,getObject()方法里调用 getEarlyBeanReference()方法 来封装成 ObjectFactory对象。
-
earlyProxyReferences 存储的是 (beanName, bean)`键值对,这里的 bean 指的是原始对象(刚实例化后的对象)。
-
wrapIfNecessary()方法用于执行 AOP 操作,生成一个代理对象(也就是说如果有 AOP 操作最后返回的是代理对象,否则返回的还是原始对象)。
最后梳理一下整体流程

先通过getSingleton()判断bean是否存在。

判断顺序:一级缓存 -> 二级缓存 -> 三级缓存。
在第三级缓存中调用了 singletonFactories.get(beanName)按照上文所说的会触发执行:如果有 AOP 操作则返回代理对象,否则返回原始对象。并且会判断取出的数据是否存在,如果存在则存入到二级缓存中,并删除三级缓存的数据。
如果缓存中都没有的话,就会去执行 createBean()创建一个 Bean 对象出来。
真正执行创建bean的是doCreateBean()

引用: