Spring三级缓存思想解决循环依赖总结分析
作者:jacheut
三级缓存思想
Spring 解决循环依赖的核心就是提前暴露对象,而提前暴露的对象就是放置于第二级缓存中。下表是三级缓存的说明:
所有被 Spring 管理的 Bean,最终都会存放在 singletonObjects 中,这里面存放的 Bean 是经历了所有生命周期的(除了销毁的生命周期),完整的,可以给用户使用的。
earlySingletonObjects 存放的是已经被实例化,但是还没有注入属性和执行 init 方法的 Bean。
singletonFactories 存放的是生产 Bean 的工厂。
解决循环依赖
Spring 是如何通过上面介绍的三级缓存来解决循环依赖的呢?这里只用 A,B 形成的循环依赖来举例:
- 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
- 为 A 创建一个 Bean 工厂,并放入到 singletonFactories 中。
- 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
- 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
- 为 B 创建一个 Bean 工厂,并放入到 singletonFactories 中。
- 发现 B 需要注入 A 对象,此时在一级、二级未发现对象 A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A 还是一个半成品,并没有完成属性填充和执行初始化方法)
- 将对象 A 注入到对象 B 中。
- 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
- 对象 A 得到对象 B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
- 对象 A 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。
我们从源码中来分析整个过程:
创建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // ① 实例化对象 instanceWrapper = this.createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null; Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null; // ② 判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到三级缓存 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 添加三级缓存的方法详情在下方 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // ③ 填充属性 this.populateBean(beanName, mbd, instanceWrapper); // ④ 执行初始化方法,并创建代理 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }
添加三级缓存的方法如下:
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); } } } @FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; }
通过这段代码,我们可以知道 Spring 在实例化对象之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。
因此,Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?
这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象?
Spring 的做法就是在 ObjectFactory 中去提前创建代理对象。它会执行 getObject() 方法来获取到 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; }
因为提前进行了代理,避免对后面重复创建代理对象,会在 earlyProxyReferences 中记录已被代理的对象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @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 需要三级缓存的目的是为了在没有循环依赖的情况下,延迟代理对象的创建,使 Bean 的创建符合 Spring 的设计原则。
如何获取依赖
通过一个 getSingleton() 方法去获取所需要的 Bean 的。
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 一级缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 三级缓存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // Bean 工厂中获取 Bean singletonObject = singletonFactory.getObject(); // 放入到二级缓存中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton() 方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 方法创建这个 Bean。
二级缓存能够解决循环依赖吗
第三级缓存的目的是为了延迟代理对象的创建,因为如果没有依赖循环的话,那么就不需要为其提前创建代理,可以将它延迟到初始化完成之后再创建。
既然目的只是延迟的话,那么我们是不是可以不延迟创建,而是在实例化完成之后,就为其创建代理对象,这样我们就不需要第三级缓存了。因此,我们可以将addSingletonFactory() 方法进行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象 object o = singletonFactory.getObject(); // 直接从工厂中获取 Bean this.earlySingletonObjects.put(beanName, o); // 添加至二级缓存中 this.registeredSingletons.add(beanName); } } }
这样的话,每次实例化完 Bean 之后就直接去创建代理对象,并添加到二级缓存中。测试结果是完全正常的,Spring 的初始化时间应该也是不会有太大的影响,因为如果 Bean 本身不需要代理的话,是直接返回原始 Bean 的,并不需要走复杂的创建代理 Bean 的流程。
测试证明,二级缓存也是可以解决循环依赖的。为什么 Spring 不选择二级缓存,而要额外多添加一层缓存呢?
如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而 Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存。但是因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。
解决循环依赖总结
Spring设计了三级缓存来解决循环依赖问题。
- 第一级缓存里面存储完整的bean实例,这些实例是可以直接被使用的;
- 第二级缓存里面存储的实例化以后但是还没有设置属性值的bean实例,也就是bean里面的依赖注入还没有做;
- 第三级缓存用来存放bean工厂,它主要用来生成原始bean对象,并且放到第二个缓存里面。
三级缓存的核心思想就是把bean的实例化和bean里面的依赖注入进行分离,采用一级缓存存储完整的bean实例,采用二级缓存来储存不完整的bean实例。通过不完整的bean实例作为突破口,解决循环依赖问题。至于第三级缓存,主要是解决代理对象的循环依赖问题。
spring无法解决的循环依赖场景
- 多实例的Setter注入导致的循环依赖,需要把bean改成单例。
- 构造器注入导致的循环依赖,可以通过@Lazy注解。
- DependsOn导致的循环依赖,找到注解循环依赖的地方,使它不循环依赖。
- 单例代理对象Setter注入导致的循环依赖,可以使用 @Lazy注解;或者使用 @DependsOn注解指定加载先后关系。
多例、构造器注入为什么不能解决循环依赖?
因为循环依赖的原理是实例化后提前暴露的引用,这两种情况还没实例化
以上就是Spring三级缓存思想解决循环依赖总结分析的详细内容,更多关于Spring解决循环依赖的资料请关注脚本之家其它相关文章!