简介
说明
本文介绍Spring(SpringBoot)为什么使用三级缓存来解决循环依赖(为什么不使用二级)。
问题引出
在上边的分析中我们可以提出两个问题:
- 二级缓存好像没有用到?那么它什么时候会用到?
- 为什么第三级缓存要用一个工厂,删除第三级缓存,只用第一二级不可以吗?
系列文章
- Spring循环依赖的原理系列(一)–什么是循环依赖 – 自学精灵
- Spring循环依赖的原理系列(二)–打断点分析 – 自学精灵
- Spring循环依赖的原理系列(三)–原理概述 – 自学精灵
- Spring循环依赖的原理系列(四)–为什么用三级缓存,而不是二级 – 自学精灵
可以去掉第三级缓存吗?
不可以去掉第三级缓存。
浅层原因
Spring 的设计原则是在 IOC 结束之后再AOP( bean 实例化、属性设置、初始化之后再通过进行AOP(生成代理对象))。即:AOP的实现需要与bean的生命周期的创建分离。
为了解决循环依赖但又尽量不打破这个设计原则的情况下,使用了第三级缓存(key:bean名字,value:ObjectFactory)。它将一个函数式接口作为ObjectFactory,相当于延迟初始化。在AOP或者解决循环依赖时,通过调用Object的getObject()方法获取到第三级缓存中的对象。
如果去掉第三级缓存,将AOP的代理工作放到第二级缓存,这样的话,bean在创建过程中就先生成代理对象再初始化和其他工作,与Spring的AOP的设计原则相违背。
深层原因
如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是否有循环依赖。正常情况下没有循环依赖,Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:
- 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
- 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。
Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存:Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
详解
上边:“bean何时被加入第3级缓存?” 我们可以得到,第三级缓存里存放的value是:AbstractAutowireCapableBeanFactory#getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean)。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
可以看到实际调用SmartInstantiationAwareBeanPostProcessor接口的getEarlyBeanReference(Object bean, String beanName)方法,此接口有以下实现类:
InstantiationAwareBeanPostProcessorAdapter
@Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { return bean; }
直接返回bean,这里看起来第三级缓存没必要存在。但是,看另一个实现类:
AbstractAutoProxyCreator
@Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }
这里把对象放入第二级缓存。
Spring之AOP的原理(一)–概述 – 自学精灵 曾分析过AOP:将切面逻辑织入目标Bean:AbstractAutoProxyCreator#postProcessAfterInitialization方法,在Bean实例化之后被调用。
来看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; }
把key对应的项从第二级缓存中移除。如果原来第二级缓存中就是这个bean,则返回此bean;否则,返回代理类。
可以去掉第二级缓存吗?
概述
不可以去掉第二级缓存。
原因
第二级缓存可以解决实例被重复代理的问题。(同时,也是设计上的需要)
详解
假如有这种情况:A同时依赖于B和C,B和C都依赖于A。即:
- A实例化时,先提前暴露objectFactoryA到第三级缓存,调用getBean(B)依赖注入B实例。
- B实例化,实例化之后,提前暴露objectFactoryB到第三级缓存,调用getBean(A)依赖注入A实例,由于提前暴露了objectFactoryA,此时从第三级缓存中获取到A实例,并将A实例放到第二级缓存。B实例完成了依赖注入,升级为一级缓存。
- A实例化开始填充属性,调用getBean(C)注入C实例
- C实例化之后,提前暴露objectFactoryC到第三级缓存,调用getBean(A)依赖注入A实例。由于第2步将A实例放到了第二级缓存,此时可以从第二级缓存中获取到A实例。
那么,如果没有第二级缓存,会如何处理呢?
- 法1:从第三级获取到实例后,放到第一级缓存
- 存在的问题:按照设计思路,第一级存放的是初始化好的对象,直接放到第一级缓存是不合适的。
- 法2:从第三级获取到实例后,不处理,下次依然从第三级缓存获取
- 如果有其他实例依赖了这个对象,会再次对这个对象进行代理,这就导致了重复代理。
请先
!