Skip to content

Bean 的依赖注入之循环依赖

循环依赖过程

循环依赖只会出现在单例实例无参构造函数实例化情况下。有参构造函数的加@Autowired 的方式循环依赖是直接报错的,多例的循环依赖也是直接报错的。循环依赖的步骤如下:

  1. A 类无参构造函数实例化后,设置三级缓存
  2. A 类 populateBean 进行依赖注入,这里触发了 B 类属性的 getBean 操作
  3. B 类无参构造函数实例化后,设置三级缓存
  4. B 类 populateBean 进行依赖注入,这里触发了 A 类属性的 getBean 操作
  5. A 类之前正在实例化,singletonsCurrentlyInCreation 集合中有已经有这个 A 类了,三级缓存里面也有了,所以这时候是从三级缓存中拿到的提前暴露的 A 实例,该实例还没有进行 B 类属性的依赖注入的,B 类属性为空。
    • 具体通过 Object sharedInstance = getSingleton(beanName);
    • 这个方法(先从一级缓存中拿,再从二级缓存中拿,如果还拿不到,并且允许 bean 提前暴露则从三级缓存中拿到对象工厂,从工厂中拿到对象成功后,升级到二级缓存,并删除三级缓存。再有别的类引用的话就从二级缓存中进行取)
  6. B 类拿到了 A 的提前暴露实例注入到 A 类属性中了
  7. B 类实例化已经完成,B 类的实例化是由 A 类实例化中 B 属性的依赖注入触发的 getBean 操作进行的,现在 B 已经实例化,所以 A 类中 B 属性就可以完成依赖注入了,这时候 A 类 B 属性已经有值了。
  8. B 类 A 属性指向的就是 A 类实例堆空间,所以这时候 B 类 A 属性也会有值了。

为什么有参构造函数的加@Autowired 的方式循环依赖是直接报错的?

  1. 创建 bean 的实例是在方法 createBeanInstance(beanName, mbd, args)中通过无参或有注解@Autowired 参构造函数实例化进行的。
  2. createBeanInstance 方法执行完毕之后才会判断是否是单例 bean 提前暴漏从而为实例化的 bean 添加三级缓存 singletonFactories
  3. 在执行 createBeanInstance 方法时,由于构造函数中的参数是引用类型的会触发 bean 的实例化,此时就会再次进行 getBean 操作,由于之前的 bean 的实例化尚未完成,三级缓存中也没法获取到,所以就继续走之前第一次实例化进入的 getSingleton 方法,此方法中的 beforeSingletonCreation 会校验该 bean 是否正在实例化,事实上这个 bean 正在实例化,因此直接抛出异常。
  4. 如果是通过属性加 @Autowired 方式进行循环依赖,在进行 populateBean 属性依赖注入之前就会把 bean 放入三级缓存中,从而进行 populateBean 操作时触发 getBean 时就可以直接从三级缓存中取就可以了。

总结:createBeanInstance 方法完成后添加三级缓存,当有循环依赖的时,再次引入该 Bean 时调用 getSingleton 方法会将三级缓存升级为二级缓存同时删除三级缓存,当 bean 执行完依赖注入以及 init 方法以后赋值给 exposedObject 返回 bean 实例对象同时销毁原 Bean 对象以后添加一级缓存同时删除二级和三级缓存。 一级缓存对应 map(singletonObjects) 二级缓存 map(earlySingletonObjects) 三级缓存 map(singletonFactories)。其实除了单例模式,多例模式也是不支持循环依赖的,原理同上,多例每次的三级缓存都不一样,所以找不到对应的三级缓存,校验的时候也会报错误。