(面试题)聊聊Spring的循环依赖问题

179 阅读4分钟
  • 是什么:在单例模式下,两个或多个Bean,相互之间持有对方的引用,会导致注入的时候产生死循环

  • 为什么:最主要的原因还是因为Spring的生命周期,实例化 -> 属性注入 -> 初始化 -> 销毁。

比如说现在有A和B两个Bean,在生成A的时候,需要属性注入B,也就是需要B初始化并放在单例池中。在生成B的时候,需要属性注入A,也就是需要A初始化并放在单例池中。

双方都需要对方先初始化完成并放在单例池中,导致程序无法继续推进,也就是循环依赖问题。

4.jpg

  • 怎么解决(重点):
    • 原理:打破这个环,将实例化和属性注入解耦

引入两个概念:

  1. 成品:完成实例化、属性注入和初始化的Bean
  2. 半成品:完成实例化,但是还没有完成属性注入和初始化的Bean

步骤:

  1. 我们可以将A在实例化,但是还没有进行属性注入的时候,将A半成品放入半成品池中
  2. 然后在B属性注入的时候,查看单例池中有没有A,如果没有就从半成品池中获取A的实例进行B的属性注入
  3. B注入完成后,放入单例池。然后A发现单例池中有B,注入完B后进入单例池

整理面试官的一些问题

# 三级缓存具体放什么对象
一级缓存放成品,也就是完整生命周期的Bean
二级缓存放办成品,也就是完成实例化,但是还没有完成属性注入的Bean
三级缓存放Bean的创建工厂,这个工厂是一个函数式接口,可以传入lambda表达式
# 三级缓存工作流程
用发生循环依赖的A和B两个Bean举例
1. A会将一个lambda函数,里面就是生成Bean的方法放入三级缓存,这个函数不会执行。
2. B在进行属性注入的时候,先查一二级缓存没有查到,最后在三级缓存找到A,就获得A代理对象并将它放到二级缓存,并删除三级缓存中的A对象工厂
3. B顺利初始化完成,B被放入一级缓存。然后A就可以从一级缓存中获取B,A也初始化完成进入一级缓存,并删除二级缓存中的A。
# 只使用一级缓存是否可以?
不可以,如果只是用一级缓存,那就说明成品和半成品放在一起,那别人来拿Bean的时候,很可能拿到半成品Bean,报空指针异常
# 只使用二级缓存是否可以?
	不可以,主要是因为AOP。如果循环依赖的Bean需要代理对象,那么就在依赖注入的时候就需要注入代理对象,所以二级缓存应该放入代理对象。如何让二级缓存中包含代理对象,就需要再加一个三级缓存,一个对象工厂存放lambda表达式,如果有代理对象就返回代理后的Bean,如果没有代理对象就返回原始Bean
	(了解)还有一些题外话,二级缓存也可以实现代理对象,但是不符合Spring设计原则,Spring是希望AOP在尽量在初始化中实现,不想提前暴漏到二级缓存,在实例化后就生成代理对象。
# 什么时候三级缓存解决不了循环依赖问题
答案:生成不了半成品的时候
	实例化和属性注入必须不能分离,也就是某些情况下不能使用构造方法注入。
	 比如A是setter注入,B是构造放方法注入,这个可以,因为A可以先创建一个半成品对象
反例:A是构造方法注入,B是任意方法注入。这个就不行,无法创建A的一个半成品对象