您现在的位置是:首页 >技术杂谈 >Spring(4) Spring是如何使用三级缓存来解决循环依赖问题?网站首页技术杂谈

Spring(4) Spring是如何使用三级缓存来解决循环依赖问题?

ACGkaka_ 2023-07-07 00:00:03
简介Spring(4) Spring是如何使用三级缓存来解决循环依赖问题?

1.什么是循环依赖?

假设我们有两个类 A 和 B,类A和类B的实例互为成员变量即为循环依赖。

当然也有可能是一个类,或三个类间进行循环依赖。

2.什么是Spring的循环依赖?

我们这里还是拿上面类 A 和 B 来举例:

补充知识:Bean的声明周期如下:

当 Spring 框架启动后,开始根据注解将类 A 实例化并注入到容器当中,在实例化 A 之后就会尝试获取类 B 的实例来进行依赖注入。

在这里插入图片描述

由于类 B 并没有进行实例化,那么 Spring 框架就会去实例化类 B,同样的也会需要将类 A 的依赖注入到类 B 的实例中:

在这里插入图片描述

最终,无论先实例化哪个类,都会形成死循环。

在这里插入图片描述

简化后如下所示:

在这里插入图片描述

3.三级缓存解决循环依赖

Spring 框架解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

// 单实例对象注册器
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
	/** 一级缓存:缓存对象单例,key: Bean的名称,value: Bean的实例 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	/** 三级缓存:缓存单例工程: key: Bean的名称,value: 生成对象的工厂 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
	/** 二级缓存:缓存早期的单例对象: key: Bean的名称,value: Bean的早期实例 */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}
缓存名称源码名称说明
一级缓存singletonObjects单例池,缓存已经初始化完成的bean对象。
二级缓存earlySingletonObjects缓存早期的bean对象。(例如:只进行了实例化,还没有进行依赖注入)
三级缓存singletonFactories缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的。

我们再看一下上面的循环依赖场景,思考一下:

为什么要使用三级缓存才能解决循环依赖呢?

3.1 假如只使用一级缓存

如果只使用一级缓存,我们可以根据上面的例子看到,类A和类B都不存在,根本没有初始化完成的对象可以存放到一级缓存中,所以循环依赖没有修复。

3.2 假如使用二级缓存

如果想打破上面循环依赖的死循环,就需要一个中间人来将已经实例化但是没有完成依赖注入的对象给缓存起来,这个中间人就是二级缓存

在这里插入图片描述

然后再配合一级缓存,我们将创建好的单例对象存放到单例池中,同时清空二级缓存中对应的原始对象(半成品实例)。

在这里插入图片描述

看到这里,我们就会有疑问,这不是一级缓存 + 二级缓存已经解决了循环依赖的问题了吗?为什么还需要三级缓存?

3.3 为什么要使用三级缓存

假如类A被增强了,那么我们需要注入到Bean容器中的就是A的代理对象,那么经过上面一整套流程下来,存放到一级缓存中的并不会是代理对象A,而是对象A。

为了将对应的代理对象A的实例也注入到容器中,这里我们就需要使用三级缓存了。

首先,我们在实例化A之后,将A中用于创建代理对象A的工厂对象 A-ObjectFactory,和B中用于创建对象B的工厂对象 B-ObjectFactor 放到三级缓存中。

并使用A的工厂对象 A-ObjectFactory 作为A的实例注入到A中。

在这里插入图片描述

然后,我们通过A的ObjectFactory对象创建A的代理对象(半成品/原始对象),然后将A的代理对象注入给B,就可以将B创建成功。

在这里插入图片描述

最后,我们将创建好的B放入单例池中,然后将B注入给A,这样我们就可以最终将A创建成功,然后将创建好的A再放入单例池中。

在这里插入图片描述

这样我们就成功使用三级缓存来解决了创建对象时的循环依赖的问题。

4.三级缓存解决循环依赖的局限性

三级缓存只是解决了构造函数之后的循环依赖问题,那么构造函数的循环依赖问题怎么解决呢?

在这里插入图片描述

Spring 给我们提供了一个 @Lazy 注解,也叫懒加载,或延迟加载。被这个注解修饰的对象,只有在使用的时候才会创建实例,那时单例池中的其他对象都已经创建好了,便解决了循环依赖的问题。

整理完毕,完结撒花~ 🌻

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。