在Spring Bean 的生命周期中,里面有一步就是填充属性。而填充属性之前会判 属性对象是否被当前对象循环依赖,当发现属性对象被循环依赖的时候会进行aop(被命中)并且生成属性对象的代理对象(未命中目标对象)。
循环依赖是如何形成的
![在这里插入图片描述](https://img-blog.csdnimg.cn/b07df36ad09241c2be90a0a198a1a8b2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
当 对象UserA 实例化完成,进行填充属性UserB 的时候 ,先去单例池里面去获取 UserB 对象,初次没有获取到,开始实例化UserB ,当UserB 实例化完成,进行填充属性UserA 的时候,先去单例池里面去获取 UserA 对象,初次没有获取到,再去实例化UserA ,并进行填充属性UserB,此时造成死循环。
单例池 存放的是经过完成生命周期的对象即最终的产出的SpringBean。
如何解决循环依赖
我们先回顾一下Spring Bean生命周期的大致步骤 ,如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/a2b74c36f8214390885c4365427f6dc6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
如何解决呢
1.添加一个缓存MapA,如图所示
![在这里插入图片描述](https://img-blog.csdnimg.cn/9c0c97a6856a46b1b0a65113b723d0d7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
当实例化UserA 的时候 先将实例化好的UserA 对象放到 MapA 中,再来填充它的UserB 属性,单例池里面没有,去实例化UserB ,并将实例化好的UserB 对象放到 MapA 中,再来填充 UserB对象的 UserA属性,单例池里面没有,去缓存 MapA 里面获取并且获取到了,直接给属性UserA 赋值,UserB 创建完成,将UserB放入单例池,再将 UserA对象 的 UserB 属性赋值,再完成UserA 的创建,最后将UserA 放入单例池。
但是根据上面的生命周期而言,循环依赖赋值的属性对象值并不是最终的那个Spring Bean ,只是一个实例对象。
就是说这个缓存里面放的并不是最终的对象(虽然内存里面都是同一份地址,但是这个对象可能被AOP表达式命中的对象呢)
那么如果 MapA 里面存放的是实例化或者是被代理的对象呢?
那次此时 放入 MapA 缓存之前还需要判断 这个对象是否需要进行代理。如果需要进行代理则存放代理对象,如果不需要代理,则存放原始目标对象即当前的实例化对象。
如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/a2334544c0e448fe973481fc18a68181.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
这样最终的放入单例池或者属性赋值的对象就是一个完整的Spring Bean了。
这样一来又有一个问题。
如何判断这个实例化的对象需要提前进行代理?
(因为正常流程生成AOP对象是在初始化之后干的事)
发生循环依赖的对象(被aop 表达式命中的对象产出循环依赖也会提前)
那又如何判断当前正在被创建的对象产生了循环依赖呢
如下图所示
![在这里插入图片描述](https://img-blog.csdnimg.cn/889de9edc12948c89bea37f51f341f92.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
当正在创建UserA对象的时候,将当前正在被创建的UserA对象放入 setA 集合中。当UserA被实例化出来,填充UserB属性,去setA 集合判断有没有,再创建UserB 对象,再填充UserB 对象的UserA 属性时,去serA 集合判断有没有,如果有即代表UserA被循环依赖了。
如下图所示
![在这里插入图片描述](https://img-blog.csdnimg.cn/36df36274a73419899267ba7eab58cff.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
那么这样是不是就解决了循环依赖的问题呢?
我们刚才的场景是这样的
![在这里插入图片描述](https://img-blog.csdnimg.cn/7317c77ac12747149b72a59f10d2e3e6.png)
那如果又依赖了UserC 呢
![在这里插入图片描述](https://img-blog.csdnimg.cn/303a172968f34fe0bb10a80516cdc097.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
当 UserA 和 UserB 发生了循环依赖问题,我们生成了一个UserA 的代理对象。那如果此时 UserA 和 UserC 也发生了循环依赖。当UserA 对象里面的UserB 属性填充完成,再继续填充UserC 的时候是不是也会生成另外一个新的代理对象了。那最终存入单例池的那个最终的User的代理对象就不是同一份呢。
当然这个问题其实在在缓存里面判断一下UserA的代理对象是否存在即可解决问题。
但是 Spring 在解决循环依赖的时候使用了三层缓存。我们当前已知的缓存只有两个 第一个单例池 和 第二个 MapA。那第三个怎么来的呢?
继续看这个图
![在这里插入图片描述](https://img-blog.csdnimg.cn/86bfd44621a5420886fef6a78e74e703.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
从实例化UserA开始到下面的UserB里面填充UserA属性,再生成UserA的代理对象,那生成UserA代理对象的目标对象怎么得到?源码中并没有把实例化好的原始UserA对象当作一个参数去导出传啊,此时加入第三个集合 MapB 就是 形成了Spring 解决循环依赖的三个集合了。
![在这里插入图片描述](https://img-blog.csdnimg.cn/ac561ebbca4341b79bfda76d10265864.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5ouf5qKm,size_20,color_FFFFFF,t_70,g_se,x_16)
Spring 解决循环依赖的三级缓存
1.singletonObjects --单例池
2.earlySingletonObjects – MapA
3.singletonFactories – MapB
单例池存放的都是最终的经过完整生命周期的Spring Bean 对象
MapA 存放的是目标对象或者代理对象
MapB 存放的是实例化的原始对象即目标对象
singletonsCurrentlyInCreation setA
源码跟踪地址:循环依赖源码流程
共勉