Spring循环依赖问题

什么是Spring循环依赖

首先,有两个类分别为A和B,类A中有一个类型为B的属性,类B中有一个类型为A的属性。见下图:

@Component
public class AService {
    @Autowired
    private BService bService;
    public void test() {
        System.out.printlen(bService);
    }
}
@Component
public class BService {
    @Autowired
    private AService aService;
}

创建A的bean时的生命周期如下:

  1. 通过构造函数实例化一个AService对象

  2. 给对象中的bService(是一个类)属性赋值(依赖注入,从单例池Map找BService的bean对象,找不到就创建一个)

    因为单例池中找不到BService的bean对象,则创建BService的bean:

    1. 通过构造函数实例化一个BService对象
    2. 给对象中的aService属性赋值
    3. 其他步骤
    4. 放入单例池Map
  3. 其他步骤(对第一步的AService对象进行初始化,AOP等操作)

  4. 将完整的AService对象放入单例池Map

通过上面的步骤可知,在AService的bean创建过程可以发现,如果在依赖注入的过程中发现单例池中找不到BService的bean,那么就尝试创建BService的bean对象,创建的过程中,依赖注入这一步又需要从单例池中查找AService的bean对象,找不到AService的bean,那么尝试创建AService的bena,反复循环…这就是Spring的循环依赖,Spring内部通过三级缓存解决了循环依赖。
在这里插入图片描述

bean创建流程图(出现循环依赖)

PS:有时候启动Spring项目时会报循环依赖的错误,这是因为用到了高版本的SpringBoot,默认不开启支持循环依赖,那么可以将相关属性(allowCircularReferences)设置为true来支持循环依赖。

如何解决循环依赖

可以通过三级缓存来解决。通过以下几个问题的逐步分析来了解三级缓存:

问题1-循环依赖

即前面所讲的Spring循环依赖问题。

解决问题

解决对策:1. 加一个ZhouyuMap来解决。

加ZhouyuMap之后,AService的创建生命周期见下图:
在这里插入图片描述

AService的创建生命周期(通过ZhouyuMap缓存解决循环依赖)

在这里插入图片描述

bean创建流程图(通过ZhouyuMap缓存解决循环依赖)

问题2-无法处理有加强方法的AService

但是新的问题来了:

  1. 生成的BService的bean对象里面的AService属性赋的不是一个真正的bean对象,而是一个刚new好的AService普通对象(没有做依赖注入)。
  2. 还有一个情况要考虑,就是如果AService有加强方法(AOP)的话,最终生成的bean对象实际上是一个代理对象bean,并将其放入单例池。同时,BService的AService属性是一个AService普通对象,而不是想要的AService代理对象,这也是一个问题。

存在加强方法的AService的创建生命周期如下,存在上面所述的两个问题:

在这里插入图片描述

AService的创建生命周期(存在加强方法)

解决问题

为了在创建BService的时候,给aService属性赋一个AService代理对象,只能考虑将AService的创建生命周期中“创建代理对象”的第三步提前,见下图:

在这里插入图片描述

判断存在循环依赖

注意,提前创建代理对象前,需要进行判断AService是否存在循环依赖这一步骤,见下图的问号处:

在这里插入图片描述

“判断AService是否存在循环依赖”这一步骤在AService的创建生命周期的第二步也会发生:执行到给aService属性赋值,在单例池中查找AService的bean时,得知AService正在创建中,那么到这里就判断出现了循环依赖,如下图:

在这里插入图片描述

如下图,加个set集合CreatingSet保存正在创建的bean,然后在给aService属性赋值这一步就能查阅set集合,最后判断出AService正在创建中,得出存在循环依赖:

在这里插入图片描述

接着:

  1. 将Aservice创建生命周期第一步中“AOP—>创建AService代理对象”的操作移到第二步中“AService正在创建中”后面
  2. 将Aservice创建生命周期第一步中“放入ZhouyuMap”操作和第二步中“ZhouyuMap—>AService普通对象”操作直接去掉

形成下图:

在这里插入图片描述

问题3

那么新的问题来了:

  1. 上图中红框处的“其他步骤(内容包括创建代理对象)”就不能执行了,需要加一个判断(也就是问号),怎么判断?
  2. 给aService属性赋值过程中产生的AService代理对象也要加到单例池中,怎么加?所以就有这两个问题。

解决问题-加二级缓存Map

解决对策,见下图:

  1. 第二步中“AService正在创建中”后面,AOP创建的代理对象或者非AOP创建的普通对象(“判断AOP或者非AOP,然后创建代理对象或者普通对象”的步骤,都在一个方法里定义)加入二级缓存Map
  2. 然后“其他步骤”判断是AOP,就不再创建代理对象,然后到“第四步”的时候,从二级缓存读取bean对象放到单例池中。

在这里插入图片描述

问题4

接下来看另一种情况:

  1. 加了带aService属性的CService类,同时AService类又加了cService属性,那么用上图的步骤,会出现给cService属性赋值的时候,重复创建AService代理对象的情况
@Component
public class AService {
    @Autowired
    private BService bService;
    @Autowired
    private CService cService;
    public void test() {
        System.out.printlen(bService);
    }
}
@Component
public class CService {
    @Autowired
    private AService aService;//AService代理对象
}

解决问题

解决对策:

  1. 见下图,改成红框处的步骤,先判断二级缓存有没有,没有就创建对象再放入二级缓存,即可解决前面的问题。

在这里插入图片描述

最后完整的图如下,只有一级缓存Map二级缓存Map,可以处理无加强方法的AService和有加强方法的AService:

在这里插入图片描述

问题5

但是,如上图所示:

  1. 创建BService的bean对象时,给aService属性赋值的时候,二级缓存中没找到AService的bean对象,然后判断是AService类是非AOP的时候,需要拿到AService普通对象,那么这里需要从哪里拿呢?

解决问题-加三级缓存Map

解决对策:

  1. 见下图红框处,在第一步的最后加了第三级缓存ZhouyuMap,用来用来存储未初始化的AService普通对象,不管有没有可能出现循环依赖,都会用到zhouyuMap进行缓存。ZhouyuMap的用途:
    1. 创建BService时,给aService属性赋值时,AService非AOP的情况下获取AService普通对象;
    2. 创建BService时,给aService属性赋值时,AService是AOP的情况下创建代理对象时也会用到AService普通对象。

在这里插入图片描述

最后完整的图如下:

在这里插入图片描述

其中lambda指的是,从三级缓存获取普通AService对象,根据AService是否为AOF来创建代理对象或者普通对象。

综上,通过三级缓存解决了所有存在的问题。

总结

通过对上面Spring循环依赖存在的所有问题的逐步分析,可了解到三级缓存是如何解决循环依赖问题的。

有三级缓存的情况下,AService的创建生命周期:

在这里插入图片描述

三级缓存:

  1. 第三级缓存:zhouyuMap,在源码中的变量名为singletonFactories,保存没有初始化(依赖注入)的普通对象。

    用于在创建AService的bean对象的过程中:1创建BService时,给aService属性赋值时,AService非AOP的情况下获取AService普通对象;2创建BService时,给aService属性赋值时,AService是AOP的情况下创建代理对象时也会用到AService普通对象。

  2. 第二级缓存:二级缓存Map,在源码中的变量名为earlysingletonObjects,之所以加early是因为二级缓存保存的是没有初始化(依赖注入)的代理对象或者普通对象。

    用于在创建AService的bean对象的过程中,要创建BService的bean对象,BService的bean对象的创建过程中又需要AService的bean对象(未完成初始化的代理对象或者普通对象),那么就将AService的bean对象存在二级缓存中。

  3. 第一级缓存:单例池Map,键值对形式为<beanName, Bean对象>,在源码中的变量名为singletonObjects,用于保存真正的bean对象(完成创建的生命周期)

总结一下:

  1. 如果AService没有AOP,那么用第一级缓存和第三级缓存,因为二级缓存存的就是普通对象,跟第三级缓存没有区别,所以二级缓存等于第三级缓存就可以。
  2. 如果AService有AOP,那么二级缓存存的是代理对象,就需要第一二三级缓存。

Spring循环依赖相关源码待研究。

参考资料

【图灵学院】周瑜老师2小时讲透三级缓存解决Spring循环依赖底层原理解析,让你面试少走99%的弯路_哔哩哔哩_bilibili P1-6

摘抄

A/B在三级缓存中的迁移说明:

非AOP

  1. 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己放到三级缓存中,初始化的时候需要 B ,去创建 B
  2. B 实例化同理 A(B 放入到了三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 然后把三级中的 A 放入二级缓存里面,并删除三级缓存中的 A
  3. B 顺利初始化完毕,将自己放到一级缓存里面(此时 B 里面的 A 依然是创建中的状态),再删除三级缓存中的 B 和尝试去删除二级缓存中的 B (此时二级缓存中只有 A )
  4. 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成 A 创建,再从二级缓存中获取 A 对象, 并将 A 自己放入到一级缓存里面,再删除三级缓存中的 A 和删除二级缓存中的 A

AOP

  1. 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己的 ObjectFactory(lambda) 放入到三级缓存中,初始化的时候需要 B ,去创建 B
  2. B 实例化同理 A (B 将自己的工厂对象( lambda )放入到了 三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 的 ObjectFactory
  3. 然后调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,返回一个 A 的代理对象,然后把 proxyA 放入二级缓存里面,并删除三级缓存中的 ObjectFactoryA
  4. B 顺利赋值完毕,通过 populateBean 返回 ,然后调用 initializeBean 方法进行初始化操作
  5. 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 proxyB ,完成注入操作及初始化。
    1. 由于完成注入操作及初始化返回的是 A 对象,不是 proxyA。再从二级缓存中获取到 proxyA 然后返回 proxyA
  6. 最后将 proxyA 放入到一级缓存中,再从二级缓存中删除 proxyA。

来源:

8.5 Spring解决循环依赖的原理(非AOP)_解决没有aop的循环依赖问题 的代码实现-CSDN博客

8.5 Spring解决循环依赖的机理(AOP)_spring循环依赖aop_伤如之何?的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值