您现在的位置是:首页 >技术杂谈 >spring解决循环依赖的三级缓存网站首页技术杂谈

spring解决循环依赖的三级缓存

图灵农场 2023-07-08 04:00:03
简介spring解决循环依赖的三级缓存

一、Spring在创建Bean的过程中分三步

  1. 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法,简单理解就是new了一个对象。
  2. 属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法,为实例化中new出来的对象填充属性和注入依赖。
  3. 初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean,执行aware接口中的方法,初始化方法,完成AOP代理。

二、三级缓存:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

singletonObjects第一级缓存,存放可用的完全初始化,成品的单例Bean。

earlySingletonObjects第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖。

singletonFactories第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。如果Bean存在AOP的话,返回的是AOP的代理对象

三级缓存的 key 是 beanName,value 是一个 lambda 表达式,这个 lambda 表达式的作用就是进行提前 AOP。

三、三个 map 的配合过程:

1、首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。

2、看对应的 Bean 是否在创建中,如果不在直接返回找不到,如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3。

3、去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存着工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。

4、如果三个缓存都没找到,则返回 null。

为了解决二级缓存中 AOP 生成新对象的问题,Spring 中的解决方案就是提前 AOP。

四、Spring 为什么不用二级缓存来解决循环依赖问题?

Spring 原本的设计是,bean 的创建过程分三个阶段:
1 创建实例 createBeanInstance – 创建出 bean 的原始对象
2 填充依赖 populateBean – 利用反射,使用 BeanWrapper 来设置属性值
3 initializeBean – 执行 bean 创建后的处理,包括 AOP 对象的产生
在没有循环依赖的场景下:第 1,2 步都是 bean 的原始对象,第 3 步 initializeBean 时,才会生成 AOP 代理对象。

循环依赖属于一个特殊的场景,如果在第 3 步 initializeBean 时才去生成 AOP 代理 bean 的话,那么在第 2 步 populateBean 注入循环依赖 bean 时就拿不到 AOP 代理 bean 进行注入。
所以,循环依赖打破了 AOP 代理 bean 生成的时机,需要在 populateBean 之前就生成 AOP 代理 bean。
而且,生成 AOP 代理需要执行 BeanPostProcessor,而 Spring 原本的设计是在第 3 步 initializeBean 时才去调用 BeanPostProcessor 的。
并不是每个 bean 都需要进行这样的处理,所以, Spring 没有直接在 createBeanInstance 之后直接生成 bean 的早期引用,而是将 bean 的原始对象包装成了一个 ObjectFactory 放到了三级缓存singletonFactories。
当需要用到 bean 的早期引用的时候,才通过三级缓存singletonFactories 来进行获取。

如果只使用二级缓存来解决循环依赖的话,那么每个 bean 的创建流程中都需要插入一个流程——创建 bean 的早期引用放入二级缓存。

实际中绝大部分的情况下都不涉及到循环依赖,而且 createBeanInstance --> populateBean --> initializeBean 这个流程也更加符合常理。

所以,猜想Spring 不用二级缓存来解决循环依赖问题,是为了保证处理时清晰明了,bean 的创建就是三个阶段: createBeanInstance --> populateBean --> initializeBean
只有碰到 AOP 代理 bean 被循环依赖时的场景,才去特殊处理,提前生成 AOP 代理 bean。

理论上来说,使用二级缓存是可以解决 AOP 代理 bean 的循环依赖的。只是 Spring 没有选择这样去实现,Spring 选择了三级缓存来实现,让 bean 的创建流程更加符合常理,更加清晰明了。

五、多例不能循环依赖

多例的情况下,每次getBean都会创建一个新的对象,那么应该引用哪一个对象呢?这本身就矛盾。多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,不能解决循环依赖。因而spring中对于多例之间相互引用是会提示错误的。

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