• 欢迎来到本博客,希望可以y一起学习与分享

spring的循环依赖

Java benz 2年前 (2021-07-30) 208次浏览 0个评论 扫描二维码
文章目录[隐藏]

什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。比如:A依赖B、B依赖C、C依赖A。

通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。
循环依赖官网说明:beans-dependency-resolution

结论

我们AB循环依赖问题只要A的注入方式是setter且singleton,就不会有循环依赖问题。也就说,这种注入方式,spring底层会解决循环依赖。

循环依赖报错

循环依赖现象在spring容器中注入依赖的对象,有2种情况:构造器方式注入依赖和以set方式注入依赖。

构造器方式注入依赖

结论:构造器循环依赖是无法解决的。你想让构造器注入支持循环依赖,是不存在的。

以set方式注入依赖

结论:以set方式注入依赖,可以解决循环依赖。

在普通的java下,并不会出现循环依赖问题

在Spring容器下,默认的单例(singleton)的场景是支持循环依赖的,不报错。原型(Prototype)的场景是不支持循环依赖的,会报错。

applicationContext.xml,scope改为prototype则会产生循环依赖错误。

运行

报循环依赖异常

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

Spring 内部通过三级缓存解决循环依赖

第一级缓存:(也叫单例池)singletoneObjects。存放已经经历了完整生命周期的Bean对象。
第二级缓存:earlySingletonObjects 。存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存:Map<String, ObjectFactory<?>> singletonFactories。存放可以生成Bean的工厂。

 

只有单例的Bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的Bean(scope=”prototype”),每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的Bean(scope=”prototype”)是没有缓存的,不会将其放到三级缓存中。

Spring 三级缓存操作

1、A 创建过程中需要B,于是A 将自己放到三级缓存里面,去实例化B。
2、B实例化的时候发现需要A ,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A。然后把三级缓存里面的这个A 放到二级缓存里面,并删除三级缓存里面的A 。
3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建状态)。然后回来接着创建A ,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

Spring解决循环依赖过程

Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化。
每次创建 bean 之前,我们都会从缓存中查下有没有该 bean ,因为是单例,只能有一个。
当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了 beanB ,接着就又去创建 beanB ,同样的流程,创建完 beanB 填充属性时又发现它依赖了 beanA 又是同样的流程。
不同的是
这时候可以在三级缓存中查到刚放进去的原始对象 beanA ,所以不需要继续创建,用它注入 beanB ,完成 beanB 的创建。
既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。

Spring解决循环依赖依靠的是 Bean 的“中间态”这个概念,而这个中间态指的是己经实例化但还没初始化的状态——>半成品
实例化的过程又是通过构造器创建的,如果 A 还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循环依赖问题,使用了三级缓存。
其中一级缓存为单例池( singletonObjects )
二级缓存为提前曝光对象( earlySingletonObjects )
三级缓存为提前曝光对象工厂( singletonFactories )。

假设 A 、 B 循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖 A ,这时候从缓存中查找到早期暴露的 A ,没有 AOP 代理的话,直接将 A 的原始对象注入B,完成 B 的初始化后,进行属性填充和初始化,这时候 B 完成后,就去完成剩下的 A 的步骤,如果有 AOP 代理,就进行 AOP 处理获取代理后的对象 A ,注入 B ,走剩下的流程。

1、调用doGetBean()方法,想要获取beanA,于是调用getSingletone()方法从缓存中查找beanA。
2、在getSingletone()方法中,从一级缓存中查找,没有,返回null。
3、doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingletone()的重载方法(参数为ObjectFactory的)。
4、在getSingletone()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法。
5、进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第4步的集合中)。判断为true,则将beanA添加到【三级缓存】中。
6、对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB。
7、调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性。
8、此时beanB依赖于beanA,调用getSingletone()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletoneObject,此时这个singletoneObject指向的就是上面在doCreateBean()方法中实例化的beanA。
9、这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中。
10、随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getSingletone()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中。


文章 spring的循环依赖 转载需要注明出处
喜欢 (0)

您必须 登录 才能发表评论!