Spring 为什么要使用三级缓存来解决循环依赖

抛出问题:
  Spring使用三级缓存解决Bean注入循环的依赖的问题网上已经有很多源码分析的文章了,那么我们来总结一下,Spring为什么要使用三个缓存(三级缓存)来解决循环依赖问题呢,为什么不能是一个缓存,两个缓存呢?

Spring中的三级缓存:

DefaultSingletonBeanRegistry类的三个成员变量命名如下:
/** 一级缓存 单例缓存池 缓存的是一个完整可用的bean实例*/
private final Map singletonObjects = new ConcurrentHashMap<>(256);


/** 三级缓存 该map用户缓存 key为 beanName  value 为ObjectFactory 可以通过ObjectFactory中的getObject()
	 方法来生成bean并包装bean,这级缓存用来处理生成需要被包装代理的类,如AOP的功能实现,个人自定义的一些
	 bean包装生成策略。
 */
private final Map> singletonFactories = new HashMap<>(16);


/** 二级缓存 ,用来缓存未创建完全的bean,即此时的bean还不可用,属性还没入完成全部注入*/
private final Map earlySingletonObjects = new HashMap<>(16);    
1. 只使用一级缓存
  1. 缓存类型为Map
      那么所有的bean都会被缓存在这个map中,有创建完成可使用的,也有未创建完整属性注入未完成的,如果这个时候程序中有获取bean的需求,拿到没有创建好完整的bean,那么在使用的过程中,就会出现问题了,如果要用某个属性,但是还未注入,就会报空指针NullPointException异常了,所以这种缓存方式肯定是不行的。

  2. 缓存类型为Map>

    @FunctionalInterface
    public interface ObjectFactory {
    
    	/**
    	 * Return an instance (possibly shared or independent)
    	 * of the object managed by this factory.
    	 * @return the resulting instance
    	 * @throws BeansException in case of creation errors
    	 */
    	T getObject() throws BeansException;
    
    }
    

      通过实现这个接口,可以自定义bean的创建逻辑,如实现代理功能之类的一些扩展。如果只使用这级缓存,每个bean都缓存一个ObjectFactory对象,那么在每次获取的时候都会生成一个对象,那么怎么保证bean的单例模式呢,至少需要一个Map来缓存已经创建好的bean,所以这种方式肯定是不行的。

2. 使用二级缓存
  1. singletonObjects 和 earlySingletonObjects 组合
      现在有A的field或者setter依赖B的实例对象,同时B的field或者setter依赖了A的实例,A首先开始创建,并将自己暴露到 earlySingletonObjects 中,开始填充属性,此时发现自己依赖B的属性,尝试去get(B),发现B还没有被创建,所以开始创建B,在进行属性填充时初始化A,就从earlySingletonObjects 中获取到了实例化但没有任何属性的A,B拿到A后完成了初始化阶段,将自己放到singletonObjects中,此时返回A,A拿到B的对象继续完成初始化,完成后将自己放到singletonObjects中,由A与B中所表示的A的属性地址是一样的,所以A的属性填充完后,B也获取了A的属性,这样就解决了循环的问题。
      似乎完美解决,如果就这么使用的话也没什么问题,但是再加上AOP情况就不同了,被AOP增强的Bean会在初始化后代理成为一个新的对象,也就是说:
      如果有AOP,A依赖于B,B依赖于A,A实例化完成暴露到二级缓存,开始注入属性,发现引用B,B开始实例化,使用二级缓存的A,B初始化完成后封装成代理对象,A再将代理后的B注入,再做代理,那么代理A中的B就是代理后的B,但是代理后的B中的A是没用代理的A。
      上面这个问题可能会有人说,为什么不在创建A的时候初始化一个A的代理对象放入earlySingletonObjects中呢?
      严格来讲,第三级缓存并非缺它不可,因为可以提前创建代理对象,提前创建代理对象只是会节省那么一丢丢内存空间,并不会带来性能上的提升,但是会破环 Spring 的设计原则,Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成)
      在Spring中,加载流程是:实例化,设置属性,初始化(即执行initializeBean方法),AOP增强。在有循环引用的时候,不得已才将刚生成的对象代理之后放入二级缓存,如果使用二级缓存,那么没有循环依赖的时候,也是这个流程,是不符合Spring的设计规范的。
    这位佬兄说的比较详细,可以参考一下:
    Spring循环依赖三级缓存是否可以去掉第三级缓存? - SegmentFault 思否

  2. singletonObjects 和 singletonFactories 组合
      A实例化,放入singletonFactories 缓存,设置B,B实例化,设置属性,拿到A,此时从singletonFactories 缓存中拿到代理后的A。由于A没加载完毕,不会放入singletonObjects 缓存。这个时候B开始设置C,C实例化,设置属性A,又去singletonFactories 缓存中拿对象A,。这个时候通过ObjectFactory的getObject()方法生成的A和B中生成的A就不是同一个对象了,出现问题。

3. 使用三级缓存,Spring的处理方式

  A实例化,放入三级singletonFactories缓存,设置属性B,B实例化放入三级singletonFactories缓存。B设置属性A,从三级singletonFactories 缓存中获取代理后的对象A,同时,代理后的A放入二级earlySingletonObjects 缓存,然后设置属性C,C实例化放入三级singletonFactories 缓存,设置属性A,此时从二级earlySingletonObjects 缓存中获取到的代理后的A跟B中的A是一个对象,属性A设置成功。然后执行后置处理器,进行aop的增强。增强后将代理的C放入到一级singletonObjects 缓存,同时删除三级singletonFactories缓存中的C。C加载完成,B得到C,B设置C成功,然后执行后置处理器,进行aop增强,将增强后的代理对象B放入到一级singletonObjects 缓存。删除三级singletonFactories缓存中的B。此时A拿到B,设置属性B成功,初始化后执行后置处理器,进行aop的增强生成最终代理,完成整个bean的创建。
  具体的代码就不分析了,网上分析的文章一大堆,在此仅做个总结和分析,有问题麻烦大家能够指出,小弟会采纳和改正。

你可能感兴趣的:(Spring,java,spring)