三级缓存解决循环依赖
循环依赖解决
1.了解循环依赖问题
在 Spring 中,如果一个 bean 尝试将自身引用注入到自身中,通常会引发循环依赖。
首先搞清楚什么是循环依赖:
两个Bean,A依赖B,B依赖A就构成了循环依赖,如下图:

同样的道理,如果在A中注入A表示A依赖A,也就构成了循环依赖。
创建A实例–》初始化A–》注入B–》创建B实例–》初始化B–》注入A
A-初始化-需要初始化完成bean->初始化B->b也要初始化其中的bean-》A-初始化-
卡入死循环
2.避免循环依赖流程
针对循环依赖的问题Spring会上边的过程调整为下边的流程:
创建A实例– -》创建B实例–》在B中注入A—*B的成员保存A的内存地址*》B初始化—成员赋值了》在A中注入B–》A初始化。
实例-内存地址
A ->注入B->初始化
B->A(地址)–{最后存储的就是A}->初始化
延迟了A的初始化只保留了地址
3.三级缓存解决循环依赖
Spring是如何做到呢?
Spring会延迟初始化,B需要注入A,此时Spring会先实例化A,把一个半成品A注入给B,延迟A的初始化。
具体的底层原理是Spring通过三级缓存实现:
1)singletonObjects缓存:这是 Spring 容器用来缓存完全初始化好的****单例 bean 实例的缓存。当一个 bean 初始化完成后,它会被放入singletonObjects缓存中。这个缓存是单例 bean 的最终缓存,也是 BeanFactory 中保存 bean 的主要缓存。
2)earlySingletonObjects缓存:这个缓存是用来保存被实例化但还未完全初始化的 bean 的引用。当一个 bean 已经被实例化(但还未初始化)时,它会被放入earlySingletonObjects缓存中。
给空间了-但是类数据没有放入
3)singletonFactories缓存:这个缓存保存的是用于创建 bean 实例的 ObjectFactory,用于支持循环依赖的延迟初始化。当一个 bean 被实例化,但尚未完全初始化时,Spring 会在singletonFactories缓存中查找该 bean 的ObjectFactory。这个ObjectFactory会在需要时被调用来完成 bean 的初始化。
类工厂
Spring 通过这三级缓存的组合,来确保在循环依赖情况下,能够正常初始化 bean。当两个或多个 bean 之间存在循环依赖时,Spring 使用 singletonFactories 缓存来存储 bean 的提供者(ObjectFactory)。当一个 bean 在初始化过程中需要依赖另一个还未初始化的 bean 时,Spring 会调用相应的 ObjectFactory 来获取对应的 bean 实例,这样就实现了循环依赖的延迟初始化。一旦 bean 初始化完成,它就会被移动到singletonObjects缓存中。
举例:
创建A实例–》创建B实例–》在B中注入A–》B初始化—》在A中注入B–》A初始化。
创建A实例(半成品),在earlySingletonObjects放入A半成品。
创建B实例(半成品),在earlySingletonObjects放入B半成品。
在B中注入A,通过singletonFactories拿到A的对象工厂,通过对象工厂拿到A的半成品注入到B中。
B初始化完成,将B从earlySingletonObjects移动到singletonObjects。
在A中注入B-,通过singletonFactories拿到B的对象工厂,通过对象工厂拿到B的成品注入到A中。
A初始化完成,将A从earlySingletonObjects移动到singletonObjects。
A是一片空间-当初始化完成后-地址就有数据了
说来说去 就是个内存地址的问题
**构造参数注入
4.构造循环依赖解决
虽然Spring可以解决上边通过成员变量注入引发的循环依赖问题,但是通过构造参数注入引发的循环依赖问题是会报错。

因为创建C需要调用构造方法,而构造方法需要依赖D,此时C是无法实例化的
上边分析Spring解决循环依赖是通过延迟初始化,当出现循环依赖问题可以注入一个半成品
但是如上半成品也无法生成
如何解决这种通过构造参数注入导致的循环依赖问题呢?
可以在C或D的任意一方注入另一方的代理对象而不是注入原始对象,如下:
假设在C的构造方法中注入D的代理对象可以写为:
在构造参数前加@Lazy注解,表示注入D的代理对象。
1 | |