Spring循环依赖三级缓存是否可以减少为二级缓存?
架构之美
文章插图
- 前言 -
提问:
我们都知道Spring通过三级缓存来解决循环依赖的问题 , 那么是不是必须是三级缓存?二级缓存不能解决吗?
要分析是否能够去掉其中一级缓存 , 我们需要先过一遍Spring是如何通过三级缓存来解决循环依赖的 。
文章插图
- 循环依赖 -
所谓的循环依赖 , 就是两个或者两个以上的bean互相依赖对方 , 最终形成闭环 。
比如“A对象依赖B对象 , 而B对象也依赖A对象” , 或者“A对象依赖B对象 , B对象依赖C对象 , C对象依赖A对象”;类似以下代码:
public class A {private B b;}public class B {private A a;}
常规情况下 , 会出现以下情况:
1、通过构建函数创建A对象(A对象是半成品 , 还没注入属性和调用init方法) 。
2、A对象需要注入B对象 , 发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后 , 会放入对象缓存里) 。
3、通过构建函数创建B对象(B对象是半成品 , 还没注入属性和调用init方法) 。
4、B对象需要注入A对象 , 发现对象池里还没有A对象 。
5、创建A对象 , 循环以上步骤 。
文章插图
- 三级缓存 -
Spring解决循环依赖的核心思想在于提前曝光:
1、通过构建函数创建A对象(A对象是半成品 , 还没注入属性和调用init方法) 。
2、A对象需要注入B对象 , 发现缓存里还没有B对象 , 将半成品对象A放入半成品缓存 。
3、通过构建函数创建B对象(B对象是半成品 , 还没注入属性和调用init方法) 。
4、B对象需要注入A对象 , 从半成品缓存里取到半成品对象A 。
5、B对象继续注入其他属性和初始化 , 之后将完成品B对象放入完成品缓存 。
6、A对象继续注入属性 , 从完成品缓存中取到完成品B对象并注入 。
7、A对象继续注入其他属性和初始化 , 之后将完成品A对象放入完成品缓存 。
其中缓存有三级:
/** Cache of singleton objects: bean name to bean instance. */private final Map singletonObjects = new ConcurrentHashMap<>(256);/** Cache of early singleton objects: bean name to bean instance. */private final Map earlySingletonObjects = new HashMap<>(16);/** Cache of singleton factories: bean name to ObjectFactory. */private final Map> singletonFactories = new HashMap<>(16);
左右滑动查看完整代码
文章插图
要了解原理 , 最好的方法就是阅读源码 , 从创建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手 。
一. 在构造Bean对象之后 , 将对象提前曝光到缓存中 , 这时候曝光的对象仅仅是构造完成 , 还没注入属性和初始化 。 public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactoryimplements AutowireCapableBeanFactory {protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {……// 是否提前曝光boolean earlySingletonExposure = (mbd.isSingleton()if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}……}}
左右滑动查看完整代码二. 提前曝光的对象被放入Map> singletonFactories缓存中 , 这里并不是直接将Bean放入缓存 , 而是包装成ObjectFactory对象再放入 。 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {// 一级缓存if (!this.singletonObjects.containsKey(beanName)) {// 三级缓存this.singletonFactories.put(beanName, singletonFactory);// 二级缓存this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}}}public interface ObjectFactory {T getObject() throws BeansException;}
左右滑动查看完整代码三. 为什么要包装一层ObjectFactory对象?如果创建的Bean有对应的代理 , 那其他对象注入时 , 注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况 , 而正常情况下(没有循环依赖情况) , Spring都是在创建好完成品Bean之后才创建对应的代理 。 这时候Spring有两个选择:
- 智能手机|双面小米:猛士回归与“屌丝”依赖
- 驱动|开源之系统:Ubuntu20.04电脑安装无线网卡驱动并解决包依赖关系
- Spring security CSRF 跨域访问限制问题
- Spring Boot搭建的一个在线文件预览系统
- 面试官:问你一个,Spring事务是如何传播的?
- 对Spring MVC接口进行Mock测试
- 湖南“速生人工林”成功验收 打造绿色高效循环产业技术创新链
- Spring Cloud Alibaba之 Sentinel
- SpringBoot+MyBatis+MySQL读写分离实现
- SpringBoot构造流程源码分析:Web应用类型推断