spring循坏依赖剖析

123 阅读3分钟

前言

循环依赖是近年比较火的面试题,正所谓温故而知新,每次看都新!所以,强烈推荐只看5.0.X版本的源码,目前高版本的需要使用jdk11有阅读障碍且要安装对应jdk才能编译;其内容大同小异。

循环依赖是什么

A -> B -> A 互相引用形成闭环即为循环依赖,图就不画了。

循环依赖的分类

1.setter注入循环: spring只能解决单例bean setter依赖注入造成的循环依赖;

2.多例模式循环:多例产生的循环依赖无法解决 (每次调用doGetBean都产生一个新的Bean,要么堆溢出要么栈溢出 因此直接Spring抛异常 BeanCurrentlyInCreationException)

3.构造器注入循环:构造方法进行依赖注入时产生的循环依赖无法解决 (把锅甩给java, new A()时构造方法去new B() 无穷无尽)

解决单例Bean循环依赖关键思想:实例化和初始化分开执行

Bean的生命周期核心方法如下图:

未命名文件.png

认识三个缓存
//一级缓存 存储完整的单例bean
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//三级缓存 存储Bean工厂
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

//二级缓存 存储半成品仅完成实例化的Bean对象
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

每个缓存存在的意义是什么?

singletonFactories : 一级缓存用于存储完整的单例Bean,常被我们口语化成称为IOC容器(Bean容器)

earlySingletonObjects :二级缓存用于存储已被实例化但未完成初始化的提前曝光对象,它为了延迟Aop代理类的代理过程,当未发生循环依赖时遵循spring最初的设计原则完成初始化后进行增强代理。同时能起到优化作用(因三级缓存需要遍历BeanPostProcess增强器

singletonFactories : 三级缓存储存的是能创建增强后的完整的Bean对象,不管是否发生循环依赖都需要加入进来,因为spring也不知道此类是否会发生循环依赖

结合上图分析:

当发生A -> B -> A 时: doGetBean(A)->....->populateBean->doGetBean(B)->....->populateBean->doGetBean(A)

populateBean时发生循环依赖时,会递归调用doGetBean,并提前暴露A放入二级缓存中 使B可以完成属性注入,因为A的生命周期还未走完 所以A只是个半成品,B完成属性注入之后继续走A的初始化流程;

三个缓存结合使用

//正常第一次调用 返回null
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) {
               //执行三级缓存工厂方法
               singletonObject = singletonFactory.getObject();
               //放入二级缓存
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}
  1. Spring 从一级缓存 singletonObjects 获取bean
  2. 如果获取不到并且对象正在创建,就会尝试从二级缓存earlySingletonObjects 获取
  3. 如果获取不到则从三级缓存singletonFactories中创建并放入二级缓存

为什么需要三个缓存?

其实二级缓存也能解决循环依赖,第一感觉三级缓存存放的工厂只使用一次完全没必要存放,只需要直接执行内部方法放到二级缓存就行了这是我最初的想法。但是这么做的话意味着所有的Aop代理类在初始化时都需要立即代理完成。这违背了spring的设计初衷。如果砍掉二级缓存的话,一个类可能被多次引用,在初始化过程多次创建,在创建过程中会遍历beanPostProcess增强器 严重拖慢启动时间。

总结

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

第一步,先获取到三级缓存中的工厂;

第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!