- 1.循环依赖的产生
- 2.Bean的生命周期总览
- 3.三级缓存
- 3.1 循环依赖的解决流程
- 3.2 singletonFactories存在的必要?
- 3.3 原型Bean的循环依赖
- 3.4 构造注入的循环依赖问题
- 4.@Async遭遇循环依赖的启动报错问题
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A A;
}
Spring默认是支持循环依赖的,不过也可以通过容器设置关闭。Spring的循环依赖是基于三级缓存的三个Map来完成的,三个Map各自有各自的作用。
2.Bean的生命周期总览- 包扫描、BeanDefinition的生成。
- 类加载、BeanDefinition准备生成对应的Bean。
- 通过构造推断,选定构造方法,实例化出一个Bean。
- 填充属性、依赖注入。
- 初始化
- 初始化后,判断该Bean是否需要AOP,如果需要则AOP。
- 将最终生成的Bean放入单例池singletonObjects这个Map中。
传统的循环依赖,不依靠外力干扰肯定是不能实现的,需要一些Map进行实例化过程中的外力干扰。Spring提出了三级缓存的概念,其实就是3个Map:
- `singletonObjects`:单例池,保存着完整实例化后的Bean,可以直接通过getBean( )获取。 - `earlySingletonObjects`:残缺单例池,保存着还没实例化完成的Bean(没有注入完成的当前 Bean或者是提前AOP的Bean)。 - `singletonFactories`:单例工厂,保存的是一个Lamda表达式,表达式的程序执行结果就是要 放入二级缓存的对象。3.1 循环依赖的解决流程
A在实例化过程中,会提前将创建Bean的一段程序代码:Lamda表达式暴露出来,这个Lamda表达式有可能用不上,也有可能用得上。将这个Lamda表达式缓存在三级缓存singletonFactories这个Map中。
如果发生了循环依赖,B在获取A的时候,先去一级缓存拿,拿不到就去二级缓存拿,如果还是拿不到,则去三级缓存拿(三级缓存一定能拿到),三级缓存拿到的是一个Lamda表达式程序,然后调用这个程序(如果A需要AOP,则生成代理,否则返回A原始对象),拿到以后放入二级缓存且删掉三级缓存,注入给B的A属性。
实例化完成后将完整的Bean,放入单例池。
因为SpringBean的声明周期中存在AOP阶段,而AOP产生的对象和原始对象是不同的对象。如果循环依赖没有第三级的缓存,那么在二级缓存里面就是原始对象,而不是最终的AOP代理对象。
当然Spring可以在实例化后,注入之前提前进行AOP的操作,但是这个流程不符合Spring的设计。
Spring对原型Bean不支持循环依赖,如果发生了会启动报错。因为原型Bean每次都会创建新的对象,包括对象里的属性都是新创建。如果发生了循环依赖,则陷入死循环无法处理而报错。
3.4 构造注入的循环依赖问题构造注入仍然不支持循环依赖,会报错。和原型Bean的创建类似,通过构造创建A对象,那么B对象必须作为入参,如果B对象都找不到那么构造都无法完成,无法进行下去。
4.@Async遭遇循环依赖的启动报错问题场景:A依赖B,B依赖A,A中方法加有@Async注解
如果@Async注解+循环依赖会造成启动报错,底层根本原因是给B的A属性注入之后,会检查这个A属性和singltonObjects单例池里A对象是否是同一个,如果不是就会抛这个异常。而@Async注解为什么会改变这个对象,是因为@Async注解是基于beanPostProcessor的,A初始化完成后会基于AOP产生新的对象。而@Transactional不会造成这个问题,是因为事务注解不是基于beanPostProcessor而是基于advisor。
解决这个问题可以使用@Lazy来解决、或者让Spring对B先初始化然后初始化A,就不会有这个问题。



