一、Bean对象的创建过程
一般的Bean对象如下:首先通过构造器构造一个普通对象,然后进行依赖注入,再进行一些初始化操作,初始化后根据AOP生成代理对象,最后再放入单例池map,这个单例池map就是一级缓存。
二、循环依赖问题
但是如果A的普通对象在依赖注入时用到了其它Bean对象,则要去单例池中进行查找,如果没有则也要进行创建,如果这个Bean反过来又依赖了A的Bean对象,那么这就构成了循环依赖。
三、三级缓存解决循环依赖
SpringBoot中是通过三级缓存来解决循环依赖的,这三级缓存都是一个Map:
- 一级缓存singletonobjects:就是前面的单例池map,用于存储已经创建完成的Bean对象。
- 二级缓存earlySingletonObjects:用于缓存提前创建的Bean对象。
- 三级缓存singletonFactories:用于缓存提前创建Bean对象的lambda表达式。
解决循环依赖的步骤:
- 比如说在创建A类的Bean对象前,会将其放到一个creatingSet中,表示这个类的Bean对象正在创建,然后再创建一个A的普通对象,用这个对象构造一个lambda表达式放到三级缓存中,这个表达式可以提前创建一个AOP代理对象。
- 如果在依赖注入时发现依赖的B对象在创建时也依赖了A,而且A还在creatingSet中,就说明发生了循环依赖,那么就会去二级缓存中去找,如果也没有就执行三级缓存中的lambda表达式,提前生成A的代理Bean对象放到二级缓存中,并注入到B对象中,这样B竟可以顺利完成Bean对象的构造。
- 然后A的普通对象的依赖注入也能顺利完成,再进行一些初始化操作,最后再将二级缓存中的代理Bean对象(内部引用了普通对象)放到一级缓存中,完成创建。
四、什么情况下的循环依赖不会自动解决
- 没有无参构造器,或者带参构造器加了@Autowired注解,Spring会自动调用带参的构造器构造普通对象,并自动注入参数,而如果这个参数对象也反过来依赖了原对象,那么这种循环依赖就没法自动解决了,因为构造器方法无法执行普通对象都没法创建出来,三级缓存中也就不存在相关的lambda表达式了。
@Lazy注解解决上述问题
在A的带参构造器方法上加上@Lazy注解,当参数对象B循环依赖了A时,会直接创建一个B的代理对象(非真正的Bean对象),那么A的Bean对象就可以顺利完成。当调用A中B代理对象的方法时,实际会调用Spring容器中的B的Bean对象的方法,这样就解决了上述循环依赖问题
参考:https://www.bilibili.com/video/BV1dP411J7tQ