【源码】Spring —— BeanFactory 解读 4 关于循环依赖

94 阅读3分钟

【源码】Spring —— BeanFactory 解读 4 关于循环依赖

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

BeanFactory 允许 循环依赖(默认允许)的情况下,Spring 是借助 三层缓存 来巧妙解决 循环依赖 问题的,本章节结合实例及文字链路描述总结下该过程

循环依赖

Spring 容器中,倘若 单例A 注入属性 单例B单例B 也注入属性 单例A,我们称这种情况为 循环依赖,如以下示例

@Component
public class A {

	@Autowired
	B b;
}

@Component
public class B {

	@Autowired
	A a;
}

容器在启动时,调用 AbstractBeanFactory#getBean 方法创建对应的 bean,我们从 A 入手

A

  • 试图从 DefaultSingletonBeanRegistry单例池 中获取,此时获取必然为 null
  • 开始创建对应的 bean,调用 AbstractAutowireCapableBeanFactory#createBean
  • 在判断未短路 生命周期 后调用 AbstractAutowireCapableBeanFactory#doCreateBean
  • 调用 AbstractAutowireCapableBeanFactory#createBeanInstance 创建对应的 实例对象
  • 重要的一步,我们默认允许 循环依赖,此时会创建一个对应的 单例工厂,将其加入 DefaultSingletonBeanRegistry 的二级缓存 singletonFactories
  • 此时调用 AbstractAutowireCapableBeanFactory#populateBean 进行 A属性填充,填充其属性 B 时,开始了 B 的创建

B

  • 试图从 DefaultSingletonBeanRegistry单例池 中获取,此时获取必然为 null
  • 开始创建对应的 bean,调用 AbstractAutowireCapableBeanFactory#createBean
  • 在判断未短路 生命周期 后调用 AbstractAutowireCapableBeanFactory#doCreateBean
  • 调用 AbstractAutowireCapableBeanFactory#createBeanInstance 创建对应的 实例对象
  • 重要的一步,我们默认允许 循环依赖,此时会创建一个对应的 单例工厂,将其加入 DefaultSingletonBeanRegistry 的二级缓存 singletonFactories
  • 此时 AbstractAutowireCapableBeanFactory#populateBean 进行 B 的属性填充,填充其属性 A 时,开始了 A 的创建

A^

  • 试图从 DefaultSingletonBeanRegistry单例池 中获取,此时虽然获取不到单例 A,但是可以获取到第一步骤中暴露的 A单例工厂,从中获取实例并加入 三级缓存(因为从 单例工厂 中获取实例可能会是一个很吃性能的操作,比如 AOP代理
  • 获取到 A 的实例,返回注入给 B

B^

  • 属性填充 完成
  • 调用 AbstractAutowireCapableBeanFactory#initializeBean 初始化 B
  • B 创建完成,并加入 单例池
  • 返回注入给 A

A^^

  • 属性填充 完成
  • 调用 AbstractAutowireCapableBeanFactory#initializeBean 初始化 A
  • A 创建完成,并加入 单例池
  • 循环依赖 解决

总结

  • 一级缓存:即我们所谓的 单例池,这里缓存的就是所有单例成品,获取时如果存在,直接返回即可
  • 二级缓存:缓存工厂,这里缓存的是用于创建对应实例的 工厂类
  • 三级缓存:上述工厂创建的实例会被缓存在此处,因为工厂创建实例有可能会十分消耗资源:比如创建 代理对象
在最新的 Spring Boot 2.6 版本中,循环依赖是默认关闭的,因此可以看出虽然 Spring 
支持循环依赖,但本质上还是不建议的

因此,代码的设计和编写上应该尽量避免循环依赖:
业务设计上我们可以尽量避免如上场景的出现:避免 AB 互相引用、避免代理类如事务场景的自
引用 等
代码设计上我们也有很多方法:比如 引入中间层 或者 注入 ApplicationContext 实例
(不过这又会造成和 Spring 框架的耦合)