spring 在依赖注入时,可能会出现相互注入的情况:
- @Service
- public class Service1 {
- @Autowired
- private Service2 service2;
-
- }
-
- @Service
- public class Service2 {
- @Autowired
- private Service1 service1;
-
- }
- 复制代码
如以上代码,在Service1 中通过@Autowird注入了Service2,在Service2 中通过@Autowird注入了Service1,这种相互注入的情况,就叫做循环依赖。
实际上,这种A持有B对象,B也持有A对象的情况,java代码是完全支持的:
- /**
- * 准备service1
- */
- public class Service1 {
- private Service2 service2;
-
- public void setService2(Service2 service2) {
- this.service2 = service2;
- }
-
- public Service2 getService2() {
- return this.service2;
- }
- }
-
- /**
- * 准备service2
- */
- public class Service2 {
- private Service1 service1;
-
- public void setService1(Service1 service1) {
- this.service1 = service1;
- }
-
- public Service1 getService1() {
- return this.service1;
- }
- }
-
- /**
- * 主方法中调用
- */
- public class Main {
- public void main(String[] args) {
- // 准备两个对象
- Service1 service1 = new Service1();
- Service2 service2 = new Service2();
- // 相互设置
- service1.setService2(service2);
- service2.setService1(service1);
- }
- }
- 复制代码
那么,在spring中,两个类相互注入对方实例的情况,会有什么问题呢?我们来看spring bean的创建过程(注意:这里我们仅分析bean的scope为singleton的情况,也就是scope为单例的情况):

这个过程中有几点需要说明下:
Service1为例,可简单理解为Service1 service = new Service1();Service1为例,Service1中通过@Autowired自动注入Service2,这一步就是给Service2赋值的过程,可简单理解为service1.setService2(service2);singletonObjects:经过上面两步后,一个java对象就变成了一个spring bean,然后保存到singletonObjects了,这是个map,key是bean的名称,value是bean,它只保存spring bean,不会只在java实例。实际上,java对象变成spring bean,不仅仅只是依赖注入,还有初始化、执行beanPorcessor方法等,由于本文是分析spring bean的循环依赖的,因此我们重点关注与循环依赖相关的步骤。
2.1 循环依赖产生的问题
了解了spring bean的产生过程之后,接下来我们就来分析下循环依赖产生的问题,在正式分析前,我们先来明确两个概念:
java对象:实际上,java中一切对象都可以称之为java对象,为了说明方便,以下提到的java对象仅指实例化完成、但未进行spring bean的生命周期对象;spring bean:是一个java对象,并且进行了完整的spring bean的生命周期对象;spring bean的创建过程如下:

对上图说明如下:
service1对象创建完成后,spring发现service1需要注入service2,然后就去singletonObjects中查找service2,此时是找不到service2,然后就开始了service2的创建过程;service2对象创建完成后,spring发现service2需要注入service1,然后就去singletonObjects中查找service1,此时是找不到service1,因为第一步中service1并没有创建成功 ,然后就开始了service1的创建过程;1,再次开始了service1的创建、属性注入过程。到这里,我们惊喜地发现,循环出现了!
2.2 引入earlySingletonObjects解决循环依赖
我们分析下,循环出现的原因在于,在service2获取service1时,由于singletonObjects中此时并不存在service1,因此会再走service1的创建过程,重新创建service1,因此,我们有个大胆的想法:如果在service1实例化后就把它保存起来,后面再再找service1时,就返回这个未进行依赖注入的service1,像下面这样:

上图中,引入了earlySingletonObjects,这也是个map,同singletonObjects一样,key是bean的名称,value 是一个未完成依赖注入的对象。
对上图说明如下:
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入;service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程;service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入;service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,此时service2就是一个spring bean了,将其保存到singletonObjects中;service2,然后将其注入到service1中,此时service1也成了一个spring bean,将其保存到singletonObjects中。经过以上步骤,我们发现,循环依赖得到了解决。
2.2 aop下的循环依赖
经过上面的分析,我们发现只要额外引入一个earlySingletonObjects后,循环依赖就能得到解决。但是,循环依赖真的得到了解决吗?spring除了ioc外,还有另一个重大功能:aop,我们来看看aop情况下出现循环依赖会怎样。
1. aop对象的创建过程
在正式介绍aop下的循环依赖前,我们先来明确两个个概念:
原始对象:区别于代理对象,指未进行过aop的对象,可以是java对象,也可以是未进行aop的spring bean;代理对象:进行过aop的对象,可以是java对象仅进行过aop得到的对象(仅进行过aop,未进行依赖注入,也未进行初始化),也可以是进行过aop的spring bean.我们先来看看aop是如何创建对象的:

相比于2.1中的流程,aop多了"生成代理对象"的操作,并且最终保存到singletonObjects中的对象也是代理对象。
原始对象与代理对象之间是什么关系呢?用代码示意下,大致如下:
- public class ProxyObj extends Obj {
-
- // 原始对象
- private Obj obj;
-
- ...
- }
- 复制代码
实际上,两者之间的关系并没有这么简单,但为了说明问题,这里对两者关系做了简化,小伙伴们只需要明白,代理对象持有原始对象的引用即可。
关于原始对象如何变成代理对象的,可以参考spring aop 之 AnnotationAwareAspectJAutoProxyCreator 分析(下)。
对以上创建过程,用java代码模拟如下:
- /**
- * 准备一个类
- */
- public class Obj1 {
-
- }
-
- /**
- * 准备一个类,内部有一个属性 Obj1
- */
- public class Obj2 {
-
- private Obj1 obj1;
-
- // 省略其他方法
- ...
-
- }
-
- /**
- * 准备Obj2的代理类,内部持有obj2的对象
- */
- public class ProxyObj2 extends Obj2 {
-
- private Obj2 obj2;
-
- public ProxyObj2(Obj2 obj2) {
- this.obj2 = obj2;
- }
-
- // 省略其他方法
- ...
-
- }
- 复制代码
接着,就是模拟“创建-->属性注入-->生成代理对象-->保存到容器中”的 流程了:
- public static main(String[] args) {
- // 准备一个容器,这里保存的是完成上述生命周期的对象
- // 1. 如果元素是原始对象,则该对象已经完成了属性注入
- // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
- Collection<?> collection = new ArrayList();
-
- // 开始 Obj2 的创建流程
- // 1. 创建 Obj2 对象
- Obj2 obj2 = new Obj2();
-
- // 2. 往 Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
- Obj1 obj1 = new Obj1();
- obj2.setObj1(obj1);
-
- // 3. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
- ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
-
- // 4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
- collection.add(proxyObj2);
-
- }
- 复制代码
上述代码中,
new Obj2()模拟对象的创建obj2.setObj1(xxx)模拟依赖注入new ProxyObj2(xxx)模拟代理对象的生成collection.add(xxx)模拟对象添加到容器中的过程模拟的流程如下:
obj2对象Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中Obj2的代理对象proxyObj2,proxyObj2中持有 Obj2的原始对象proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时仔细分析上面的步骤,就会发现,上面的第2步与第3步完全调换顺序也没问题,代码模拟如下:
- public static main(String[] args) {
- // 准备一个容器,这里保存的是完成上述生命周期的对象
- // 1. 如果元素是原始对象,则该对象已经完成了属性注入
- // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
- Collection<?> collection = new ArrayList();
-
- // 开始 Obj2 的创建流程
- // 1. 创建 Obj2 对象
- Obj2 obj2 = new Obj2();
-
- // 2. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
- ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
-
- // 3. 往 obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
- Obj1 obj1 = new Obj1();
- // 这里是注入到原始对象中
- obj2.setObj1(obj1);
-
- // 4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
- collection.add(proxyObj2);
-
- }
- 复制代码
上述代码的流程如下:
从代码上看,proxyObj2(代理对象)中持有ob2(原始对象),生成代理对象后,继续对原始对象进行属性注入,依然能影响代理对象,最终代理对象持有的原始对象也完成了依赖注入,整个过程用图形示意如下:

这里我们再次申明,从java对象到spring bean的步骤有好多,这里我们仅关注与循环依赖相关的步骤,如果想了解spring bean详细的初始化过程,可查看 spring启动流程之启动流程概览。
到这里,我们探索到代理对象的生命周期可以有两种:
这两种都能达到最终目的,即保存到容器中的是代理对象,且代理对象对应的原始对象完成了依赖注入。请牢记这两个创建流程,这是后面解决aop下循环依赖问题的核心,说白了,aop下的循环依赖问题之所以能解决,就是因为对象可以提前进行aop操作。
2. 为什么用earlySingletonObjects无法解决循环依赖?
前面我们主要说明了代理对象的创建过程,接下来我们来看看在aop下,使用earlySingletonObjects来解决循环依赖有什么问题:

我们来分析上图的流程:
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入;service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程;service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入;service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,然后再进行aop,此时service2是一个代理对象,将其保存到singletonObjects中;service2的代理对象,然后将其注入到service1中,接着再对service1进行aop,此时service1也成了一个spring bean,将其保存到singletonObjects中。上述步骤有什么问题呢?仔细看第4步,就会发现,注入到service2的service1并不是代理对象!纵观全局,最终得到的service1与service2都是代理对象,注入到service2的service1应该也是代理对象才对。因此,在aop下,循环依赖的问题又出现了!
2.3 spring 的解决方案
前面我们提到,在aop下,引入earlySingletonObjects并不能解决循环依赖的问题,那spring是怎么解决的呢?spring再次引入了一个map来解决这个问题,这也是人们常说的spring三级缓存,对这三个map说明如下:
singletonObjects:类型为ConcurrentHashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value是完整的spring bean,即完成属性注入、初始化的bean,如果bean需要aop,存储的就是代理对象;earlySingletonObjects:类型为HashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value是实例化完成,但未进行依赖注入的bean,如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入;singletonFactories:类型为HashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean), getEarlyBeanReference中的bean是刚创建完成的java bean,没有进行spring依赖注入,也没进行aop(关于这个lambda表达式,后面会继续分析)。为了说明方便,下面对singletonObjects、earlySingletonObjects和singletonFactories分别称为一级缓存、二级缓存和三级缓存。
spring解决aop下的循环依赖流程如下:

这个图看着比较复杂,其实分开来看就比较简单了,上述操作中,1~8是获取service1的流程,5.1~5.8是获取service2的流程,5.5.1是再次获取service1的流程,只不过在处理service1的初始化过程中,会触发service2的初始化过程,而service2的初始化时,又会依赖到service1,因此才看着像是连在一起,比较复杂。
对上图的过程,这里说明如下(建议:如果觉得流程比较复杂,可以先看1~8的操作,再看5.1~5.8的操作,最后两者联合起来看,这样会清晰很多):
service1:获取service1,从一级缓存中获取,此时是获取不到的;service1:创建service1的实例;service1:获取需要注入的属性与方法(在原始对象上进行获取);service1:如果开启了支持循环依赖的配置,就将service1放到三级缓存中(是否支持循环依赖,是可以配置的);service1:对service1进行依赖注入,需要service2,然后就开始了service2的获取流程;service2:获取service2,从一级缓存中获取,此时是获取不到的;service2:创建service2的实例;service2:获取需要注入的属性与方法(在原始对象上进行获取);service2:如果开启了支持循环依赖的配置,就将service2放到三级缓存中(是否支持循环依赖,是可以配置的);service2:对service2进行依赖注入,需要service1,然后就开始了service1的获取流程;service1: 获取service1,从一级缓存中获取,获取不到;此时发现service1正在创建中,于是继续从二、三级缓存中获取,最终从三级缓存中获取到了,将其放入二级缓存。从三级缓存获取的过程中,会判断service1是否需要进行aop,然后开始aop操作,因此放入二级缓存中的是service1代理代理,提前进行aop是解决循环依赖的关键;service2:得到了service1后(这里的service1是代理对象),将其注入到service2中,接着对service2进行aop,得到service2的代理对象;service2:如果支持循环依赖,先从一、二级缓存中再次获取service2,都未获取到,就使用当前service2(当前service2是代理对象);service2:将service2的代理对象放入一级缓存中,删除二、三级缓存,至此,service2初始化完成,注入的service1是代理对象,一级缓存中的service2也是代理对象;service1:回到service1的生命周期,拿到service2(这里的service2是代理对象)后,将其注入到service1,service1的依赖注入完成,进行初始化,这里会判断service1是否需要进行aop,虽然service1是需要进行aop的,但由于在5.5.1已经进行过aop了,因此,这里直接返回(到这一步,service1还是原始对象);service1:如果支持循环依赖,先从一级缓存中获取service1,获取不到;再从二缓存中获取service1,可以获取到(从5.5.1可知,二级缓存里是service1代理对象),返回;service1:将二级缓存中获取的对象注册到一级缓存中,删除二、三级缓存,至此,service1初始化完成,注入的service2是代理对象,一级缓存中的service1也是代理对象。以上流程,虽然步骤较多,但service1与service2的获取步骤是相同的,只要弄清了其中之一的获取流程,另一个bean的获取流程就很雷同了。
在上述流程中,还有两个数据结构需要说明下:
singletonsCurrentlyInCreation:类型为SetFromMap,位于DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的set,存储的是正在创建中的对象,判断当前对象是否在创建中就是通过查找当前对象是否在这个set中做到的;earlyProxyReferences:类型为ConcurrentHashMap,位于AbstractAutoProxyCreator,存储的是提前进行aop的对象,如果一个对象提前进行了aop,在后面再次aop时,会通过判断对象是否在earlyProxyReferences中而确定要不要进行aop,以此来保证每个对象只进行一次aop。至此,spring一共提供了5个数据结构来辅助解决循环依赖问题,总结如下:
| 结构 | 说明 |
|---|---|
singletonObjects | 一级缓存,类型为ConcurrentHashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value是完整的spring bean,即完成属性注入、初始化的bean,如果bean需要aop,存储的就是代理对象 |
earlySingletonObjects | 二级缓存,类型为HashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value是实例化完成,但未进行依赖注入的bean,如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入 |
singletonFactories | 三级缓存,类型为HashMap,位于DefaultSingletonBeanRegistry类中,key为beanName,value存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean),getEarlyBeanReference(xxx)中的bean是刚创建完成的java bean,没有进行spring依赖注入,也没进行aop |
singletonsCurrentlyInCreation | 类型为SetFromMap,位于DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的set,存储的是正在创建中的对象,可以用来判断当前对象是否在创建中 |
earlyProxyReferences | 类型为ConcurrentHashMap,位于AbstractAutoProxyCreator,存储的是提前进行aop的对象,可以用来判断bean是否进行过aop,保证每个对象只进行一次aop |
以上就是spring 解决循环依赖的完整流程了。
在正式分析源码前,我们首先模拟循环下依赖解决的过程,代码如下:
- /**
- * 准备一个类,内部有一个属性 Obj2
- */
- public class Obj1 {
- // 需要注入 obj2
- private Obj2 obj2;
-
- // 省略其他方法
- ...
- }
-
- /**
- * 准备一个类,内部有一个属性 Obj1
- */
- public class Obj2 {
- // 需要注入 ob1
- private Obj1 obj1;
-
- // 省略其他方法
- ...
-
- }
-
- /**
- * 准备Obj2的代理类,内部持有obj2的对象
- */
- public class ProxyObj2 extends Obj2 {
- // obj2代理类内部持有obj2的原始对象
- private Obj2 obj2;
-
- public ProxyObj2(Obj2 obj2) {
- this.obj2 = obj2;
- }
-
- // 省略其他方法
- ...
-
- }
-
- /**
- * 准备Obj1的代理类,内部持有obj1的对象
- */
- public class ProxyObj1 extends Obj1 {
- // obj2代理类内部持有obj1的原始对象
- private Obj1 obj1;
-
- public ProxyObj1(Obj1 obj1) {
- this.obj1 = obj1;
- }
-
- // 省略其他方法
- ...
-
- }
- 复制代码
Obj1 与 Obj2, 其中Obj1有个属性为Obj2,Obj2中有个属性为Obj1;Obj1 与 Obj2的代理类ProxyObj1、ProxyObj2,并且ProxyObj1、ProxyObj2分别有一个属性:Obj1 、 Obj2;new ObjX()模拟对象的创建;objX.setObjX(xxx)模拟依赖注入;new ProxyObjX(xxx)模拟代理对象的生成;collection.add(xxx)模拟对象添加到容器中的过程;我们模拟最终得到的结果为:
proxyObj1,proxyObj2obj1中的是proxyObj2,注入到obj2中的是proxyObj2准备工作已经完成了,接下来我们就开始进行模拟了。
3.1 模拟1
要求:
目标:
proxyObj1,proxyObj2obj1中的是proxyObj2,注入到obj2中的是proxyObj2代码如下:
- public static main(String[] args) {
- // 准备一个容器,这里保存的是完成上述生命周期的对象
- // 1. 如果元素是原始对象,则该对象已经完成了属性注入
- // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
- Collection<?> collection = new ArrayList();
-
- // 1. 创建 Obj1 对象
- Obj1 obj1 = new Obj1();
-
- // 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
- // 一. 创建 Obj2 对象
- Obj2 obj2 = new Obj2();
-
- // 到这里,obj2需要注入obj1的代理对象,但此时容器中并没有obj2的代理对象,于是又要切到obj1的创建流程
-
- }
- 复制代码
在执行以上流程中 ,发现创建 Obj2 对象后,流程就进行不下去了:
obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;obj2需要注入obj1的代理对象,但找不到,于是切换到obj1的创建流程;obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;如此循环往复。
模拟结果:未达到预期目标,本次模拟宣告失败。
3.1 模拟2
要求:
目标:
proxyObj1,proxyObj2obj1中的是proxyObj2,注入到obj2中的是proxyObj2示例代码如下:
- public static main(String[] args) {
- // 准备一个容器,这里保存的是完成上述生命周期的对象
- // 1. 如果元素是原始对象,则该对象已经完成了属性注入
- // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
- Collection<?> collection = new ArrayList();
-
- // 1. 创建 Obj1 对象
- Obj1 obj1 = new Obj1();
-
- // 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
- // 一. 创建 Obj2 对象
- Obj2 obj2 = new Obj2();
-
- // 2. 对 Obj1 提前代理
- ProxyObj1 proxyObj1 = new ProxyObj1(obj1);
-
- // 二. 将 proxyObj1 注入到 obj2 中
- obj2.setObj1(proxyObj1);
-
- // 三. 生成 obj2的代理对象
- ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
-
- // 四. proxyObj2 已经走完了完整的生命周期,将代理对象添加到容器时
- collection.add(proxyObj2);
-
- // 此时容器中已经有 obj2 的代理对象了,继续obj1的生命周期
- // 3. 将 proxyObj2 注入到 obj1 中
- obj1.setObj2(proxyObj2);
-
- // 4. proxyObj1 已经走完了完整的生命周期,将代理对象添加到容器时
- collection.add(proxyObj1);
- }
- 复制代码
上面的代码中,obj1的流程用“1,2,3,4”标识,obj2的流程用“一,二,三,四”标识,两者流程如下:
最终两者都存入了容器中,达到了预期的目标。
3.3 从模拟中得到的结论
对比上面两个模拟代码,发现模拟2之 所以能达到预期目标,主要是因为在注入obj2的obj1属性时,提前生成了obj1的代理对象proxyObj1,使得obj2能完成整个创建流程。这里再次证明,提供进行aop对循环依赖的解决起到至关重要的作用!
限于篇幅,本文就先到这里了,本文主要分析了循环依赖的产生,介绍了spring解决循环依赖的步骤,最后通过两段代码模拟了循环依赖的解决,下一篇文章我们将从spring源码分析spring是如何解决循环依赖的。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。