Spring为什么使用三级缓存而不是两级解决循环依赖问题?

【Spring为什么使用三级缓存而不是两级解决循环依赖问题?】首先明确一点,Spring如果使用二级缓存也是完全能够解决代理bean的循环依赖问题的 。那Spring为什么要使用三级缓存的设计呢?在回答这个问题前我们先明确一些概念 。
Spring Bean相关的知识Spring Bean 的创建过程

Spring为什么使用三级缓存而不是两级解决循环依赖问题?

文章插图
  1. 扫描xml或者注解获取BeanDefinition;
  2. 实例化bean:通过createBeanInstance方法创建bean的原始对象BeanWrApper;
  3. 注入bean的依赖:利用populateBean方法(本质是反射)注入bean的依赖属性;
  4. 初始化bean:调用initializeBean方法最终形成完整的bean对象;
Spring Bean 的三级缓存定义三级缓存的查找策略是,先从一级缓存获取,若获取不到就从二级缓存,仍然获取不到则从三级缓存获取,若还是获取不到则通过bean对应的BeanDefinition信息实例化 。
Tips:二、三级缓存会在DI的过程中被删除,最终所有的Bean都会变成完整的bean并存入一级缓存中 。
  • 三级缓存singletonFactories:在注入bean的依赖前存入,所有bean都会存入,循环依赖时会使用,代码如下:
/** Cache of singleton factories: bean name --> ObjectFactory */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  • 二级缓存earlySingletonObjects:存放实例化的对象(可能是原始对象也可能是代理对象),代码如下:
/** Cache of early singleton objects: bean name --> bean instance */private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
  • 一级缓存singletonObjects:用于存放完整的bean,代码如下:
/** Cache of singleton objects: bean name --> bean instance */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);什么是循环依赖?循环依赖是指:Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动完成后这俩个对象都必须是完整的bean 。
Spring为什么使用三级缓存而不是两级解决循环依赖问题?

文章插图
循环依赖的场景有三种:
  • 构造器循环依赖:Spring无法解决,因为bean创建的第一步就是通过构造器实例化,也就是说解决循环依赖的前提就是对象可以实例化并缓存,与JAVA死锁很像;
  • prototype范围的依赖:该循环依赖Spring不可解决,prototype作用域的bean Spring不缓存,因此在依赖注入时无法获取到依赖的bean;
  • setter循环依赖:该循环依赖是Spring推荐的方式,我们接下来就重点讲解这种方式;
一个简单setter循环依赖的代码示例如下:
@Servicepublic class A {// @Autowired也行@Resourceprivate B b;}@Servicepublic class B {// @Autowired也行@Resourceprivate A a;}Spring 是如何利用多级缓存解决循环依赖的我们先抛开Spring的实现来做一次解决循环依赖的设计推演 。
在没有缓存的情况下循环依赖的场景
Spring为什么使用三级缓存而不是两级解决循环依赖问题?

文章插图
如图可以直接观察到,当没有缓存时,当发生循环依赖时直接死循环了,最终的结局就是StackOverflow或者OOM 。
增加一层缓存为了解决上面循环依赖的问题,我们加入一层缓存,缓存可以使用Map结构,key为beanName,value为对象实例 。如下如:
Spring为什么使用三级缓存而不是两级解决循环依赖问题?

文章插图
从上图可以直观的看出,循环依赖的问题已经得到了完美解决,但是又有了一个新问题,这个缓存中的bean可能有已经创建完成的、正在创建中还没有注入依赖的,它们都掺杂在一起,我们如何保证Map里面的所有对象是完整的呢?一层缓存很显然不符合设计规范,也缺乏安全性与扩展性 。
二级缓存设计我们希望的是,明确已经构建完成的的Bean被放入到一个缓存中,创建中的bean在另外一个缓存中,于是就有了下面的结构:
Spring为什么使用三级缓存而不是两级解决循环依赖问题?


推荐阅读