大家好,今天跟大家聊聊Spring框架里一个让人头疼的问题——循环依赖。这就像两只小狗互相追着尾巴咬,没完没了。循环依赖指的是两个或多个Bean之间互相依赖,形成一个闭环。简单来说,就是A依赖B,B依赖C,C又依赖A,这就套娃了,形成了循环依赖。Spring循环依赖会导致Bean无法正常创建,程序启动失败或者抛出异常。所以,我们要尽量避免这种情况。
循环依赖的常见原因
构造器循环依赖
用构造器注入Bean的时候,如果两个Bean互相依赖,就会形成构造器循环依赖。就像两个小孩子,一个说“你先给我玩具,我才给你糖”,另一个说“你先给我糖,我才给你玩具”,僵持不下。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
这段代码中,A依赖B,B也依赖A,形成了循环依赖。Spring容器启动时,要先创建A,但是创建A需要B,创建B又需要A,这就死循环了。
属性循环依赖
使用属性注入Bean时,如果两个Bean互相依赖,也会形成属性循环依赖。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
Spring在创建A时要注入B,注入B时又要注入A,又循环了。
Spring如何解决循环依赖
Spring创建Bean的过程
Spring创建Bean的过程就像工厂生产产品一样,有几个步骤:
加载Bean定义: Spring容器启动后,会加载Bean的配置信息,就像工厂拿到产品的设计图纸。 创建Bean实例: 根据配置信息,通过反射创建Bean实例,就像工厂根据图纸生产产品。 属性装配: 给Bean注入依赖的属性,就像给产品组装零件。循环依赖的解决主要就在这一步。 初始化Bean: 执行Bean的初始化方法,就像产品出厂前的最后检测。
Spring的三级缓存机制
Spring能解决的循环依赖
注意:只有单例的Bean才存在循环依赖问题,Spring才能解决。原型(Prototype)类型的Bean,Spring会直接抛出异常。
Spring不支持构造器注入的循环依赖。如果A和B循环依赖,一个是构造器注入,一个是setter注入,会怎么样呢?
A构造器注入B,B setter注入A:可以解决。 B构造器注入A,A setter注入A:不行。
简单来说,setter注入的循环依赖Spring可以解决,构造器注入不行。构造器和setter混合的情况,得看具体情况。
三级缓存机制
Spring用三级缓存来解决循环依赖,就像工厂用了三个仓库:
一级缓存(singletonObjects): 存放完整的Bean实例,就像成品仓库。 二级缓存(earlySingletonObjects): 存放实例化但未初始化的Bean实例,就像半成品仓库。 三级缓存(singletonFactories): 存放Bean的创建工厂,就像零件仓库。
三级缓存解决循环依赖的过程
假设A和B循环依赖:
创建A实例,将A的工厂放入三级缓存,就像把A的零件放入零件仓库。 A注入属性时,发现依赖B,就去创建B。 B注入属性时,发现依赖A,就从缓存中找A。先从一级缓存找,没有;再从二级缓存找,也没有;最后从三级缓存找到A的工厂,用工厂创建A的早期实例,并将A的早期实例放入二级缓存,同时删除三级缓存中的A工厂。 B创建完成,放入一级缓存。 A继续注入属性,从一级缓存中找到B,A创建完成,放入一级缓存。
温馨提示
循环依赖尽量避免,可以通过重新设计代码结构来解决。 Spring只能解决单例Bean的setter注入循环依赖。
总结
循环依赖是Spring中一个比较复杂的问题,理解Spring的三级缓存机制是解决循环依赖的关键。希望这篇文章能帮助大家更好地理解Spring循环依赖。