简介
说明
本文介绍Spring(SpringBoot)为什么使用三级缓存来解决循环依赖(为什么不使用二级)。
问题引出
在上边的分析中我们可以提出两个问题:
- 为什么第三级缓存要用一个工厂,删除第三级缓存,只用第一二级不可以吗?
- 第二级缓存好像没有用到?那么它什么时候会用到?
系列文章
- Spring循环依赖的原理系列(一)–什么是循环依赖 – 自学精灵
- Spring循环依赖的原理系列(二)–打断点分析 – 自学精灵
- Spring循环依赖的原理系列(三)–原理概述 – 自学精灵
- Spring循环依赖的原理系列(四)–为什么用三级缓存,而不是二级 – 自学精灵
可以去掉第三级缓存吗?
不可以去掉第三级缓存。
概述
Spring 的设计原则是在 IOC 结束之后再AOP( bean 实例化、设置属性、初始化之后再进行AOP(生成代理对象))。即:AOP的实现需要与bean的生命周期的创建分离。
为了解决循环依赖但又尽量不打破这个设计原则的情况下,使用了第三级缓存(key:bean名字,value:ObjectFactory)。它将一个函数式接口作为ObjectFactory,相当于延迟代理这个操作。在AOP或解决循环依赖时,调用ObjectFactory#getObject()方法获取到第三级缓存中的对象(会进行代理,再返回)。
如果去掉第三级缓存(将Bean进行代理后直接放到第二级缓存),bean在创建过程中就先生成代理对象再初始化等,与Spring的AOP的设计原则相违背。
详解
若没有循环依赖,Spring在创建好成品Bean之后才创建对应的代理。
若创建的Bean有对应的代理,其他对象注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是否有循环依赖。这时Spring有两个选择:
- 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接取到代理对象。
- 不提前创建代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。
Spring选择了第二种方式,那怎做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才用ObjectFactory.getObject方法实时生成代理对象,并将生成好的代理对象放入到第二级缓存:Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Spring之AOP的原理(一)–概述 – 自学精灵 曾分析过AOP的原理:将切面逻辑织入目标Bean的代码是:在Bean实例化之后调用AbstractAutoProxyCreator#postProcessAfterInitialization方法。
来看postProcessAfterInitialization
@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
调用到wrapIfNecessary(Object bean, String beanName, Object cacheKey)
里边会对bean进行代理。
可以去掉第二级缓存吗?
概述
不可以去掉第二级缓存。
原因
第二级缓存可以解决实例被重复代理的问题。
详解
假如有这种情况:A同时依赖于B和C,B和C都依赖于A。即:

- A实例化,暴露objectFactoryA到第三级缓存,填充B实例。
- B实例化,暴露objectFactoryB到第三级缓存,填充A实例,从第三级缓存中获取到A实例,将A实例放到第二级缓存。B实例完成依赖注入,放到一级缓存。
- A继续填充属性C,
- C实例化,暴露objectFactoryC到第三级缓存,填充A实例。由于第2步将A实例放到了第二级缓存,此时可以从第二级缓存中获取到A实例。
如果没有第二级缓存,会怎样呢?
- 法1:从第三级获取到实例后,放到第一级缓存
- 缺陷:按设计思路,第一级存放的是初始化好的对象,直接放到第一级缓存不合适。
- 法2:从第三级获取到实例后,不处理,下次依然从第三级缓存获取
- 缺陷:如果有其他实例依赖了这个对象,会再次对这个对象进行代理,这就导致了重复代理。
请先
!