目录
什么是Bean的循环依赖
在Spring容器中,当存在两个或多个Bean之间相互依赖时,就会出现循环依赖的问题。如果没有正确地处理这种情况,则会发生自引用异常,导致应用程序无法启动。
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。我想创建出来得初始化你,而你的初始化又需要我,这样子都别玩了!!!
在Spring中的循环依赖主要分为三大类:
单例Bean的set注入产生的循环依赖(全是单例)(不会报错)
单、多例Bean的set注入产生的循环依赖(一单一多)(不会报错)
多例Bean的set注入产生的循环依赖(全是多例)(会报错)
单例Bean的构造注入产生的循环依赖 (会报错)
单例Bean的set注入不会产生循环依赖 (多个都是单例)
singleton + setter模式下的循环依赖是没有任何问题的,这里得力于两个特性:
1.单例Bean采用单例模式产生,其在整个Spring容器中是唯一的
2.在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean 实例化之后,不等属性赋值,马上进行"曝光"(可以理解为申请了空间有了地址,但是里面还没东西)。
3.set注入可以实现实例化和属性赋值的操作分离开,从而可以实现"曝光"操作。
通过曝光、set注入的特性,可以让我们对循环依赖的过程中的矛盾问题耍个小聪明,我要你的东西,你给了我个假的,你要我的东西时候,就拿到我真的东西,最后再悄悄咪咪把我拿到的假东西变为了真东西。
给我仔细理解这个情况,理解了这个就明白后面三种为什么会导致循环依赖报错了
多例Bean的set注入会产生循环依赖(多个都是多例)
如果两个都是多列Bean,就会报错,原因是多例Bean每次获取的对象都不是同一个
之所以第一种情况可以避免陷入循环依赖的死循环,而这种情况不行是因为第一种情况是每个Bean只有唯一的一个对象,你看,我一个Bean被你通过"曝光"的方式来混过去了,自愿被创建了出来,在依赖它的其它Bean就可以拿着这个对象来完善自身的属性,从而避免了死循环。但是你看,由于多例Bean无法保证创建的Bean是同一个,所以会导致每次通过"曝光"附上属性值的Bean不是同一个,所以对于每次指向的Bean都是一个空Bean,每一次都要重新曝光,一直循环下去。
单、多例Bean的set注入不会产生的循环依赖
针对上述多例Bean的循环依赖问题,我们不难发现,它缺的是一个"天选之子"来打破僵局,只要有一个确定了,能被别人唯一依赖了,就可以摆脱死循环下去了。因为单例的Bean的对象只有一个你再继续调该单例Bean,由于它是同一个,也就说说它的属性都已经赋好值了,就不会再调回来再创建多例Bean
单例Bean的构造注入会产生的循环依赖
这种情况会导致循环依赖异常,主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的
要知道,我们单例Bean的set注入之所以循环依赖不会报错是围绕这”曝光“这种方式来让对方能够提前获取我的一个空对象,后面再把该空对象填充好就好了,但是这种方式的实现前提在于它的实例化过程和属性赋值的过程是分离的才行,但是构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,所以就不能提前"曝光",因此就over啦
无论你单/多例,因为在构造注入中,你根本无法实例化对象,所以说你连曝光都做不到
三级缓存
单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象(空架子)【二级缓存】
单例工厂缓存:key存储bean名称,value存储该Bean对应的被提前曝光的ObjectFactory对象【三级缓存】
来看看源码(看不懂就记结论去~):
从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题
总结
当Bean是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
而在SpringBoot中使用@Lazy注解可以解决循环依赖问题,@Lazy注解的原理是通过将Bean组件延迟初始化,由Spring框架维护一个特殊的代理类作为要延迟的Bean的替身,然后在程序访问该Bean时,Spring框架实际上会去调用这个代理类去获取相应的Bean实例。其实本质和我们上面提到的"曝光"的效果都是差不多的啦