使用@Lazy解决循环依赖问题
现象
现在有两个Bean,一个叫beanA
,一个叫beanB
,beanA
通过构造器依赖beanB
,beanB
通过 @Autowrited依赖beanA
:
- BeanA.java
@Component public class BeanA { private final BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } }
- BeanB.java
@Component public class BeanB { @Autowired private BeanA beanA; }
问题
上面的这两个Bean在启动时会被Spring提示循环依赖,服务启动失败。
探索循环依赖产生的原因
首先,我们先来探究一下这是一个什么样的循环依赖:
- Spring通过
AbstractAutowireCapableBeanFactory#createBeanInstance
创建BeanA
的实例对象 BeanA
没有默认的构造器,也没有被@Autowrited
的修饰的构造,所以Spring会选择带有BeanB
的构造器来实例化BeanA
- Spring开始实例化
BeanB
,BeanB
直接使用默认构造器实例化 - 实例化完成后,开始对
BeanB
执行AbstractAutowireCapableBeanFactory.populateBean
,这时会由AutowiredAnnotationBeanPostProcessor
来处理BeanB
上的@Autowired
注解。 - 由于BeanB @Autowired了BeanA,所以此时
BeanB
需要BeanA
的实例来完成自己的依赖注入。 - 但是,此时
BeanA
已经开始实例化,但是还没有实例化完成(因为需要构造器注入BeanB
) - 所以,就产生了循环依赖
破除循环依赖
那么要如何破除循环依赖呢?常见的办法有两种:
- 修改代码逻辑
1. 从逻辑上进行整改,从而避免循环依赖的产生
2. 不要使用构造器依赖,至少让bean可以被实例化 - 使用代理
使用 @Lazy 其实用的就是代理的方式来解决循环依赖的。
@Lazy解决循环依赖的方式
接下来我们debug看下为什么@Lazy
能解决循环依赖
- 在创建
beanA
时,Spring依旧找到了带有BeanB
的构造器
- Spring依然需要实例化一个
BeanB
出来,但是因为BeanA
的构造器上写了 @Lazy,所以Spring为BeanB
创建了一个代理,并返回了。
- 接下来
BeanB
正常进行BeanB
的初始化,当处理BeanB
的 @Autowired BeanA时,由于BeanA
已经创建完毕,所以BeanB
也就可以正常创建了。 - 因为
BeanA
中的beanB
其实是一个BeanB
的代理,所以 beanA.getBeanB() 一定和beanB不是一个对象
- 但是 beanA.getBeanB().getBeanA() 却和beanA是同一个对象
- 这是因为在执行代理对象的
getBeanA()
时,会触发TargetSource的getTarget()
方法,从而返回真正的beanB
所以beanA.getBeanB().getBeanA()和beanA指向的是同一个对象。
总结
- 其实在SpringBoot 2.6.0之后Spring官方已经不建议循环依赖了,出现循环依赖还是最好从编码层面做解耦比较好。
- 如果项目比较复杂,解耦不易的话,可以采用 @Lazy的方式解决一部分循环依赖的问题。但是要注意 @Lazy会使用代理,用代理有用代理的坑,比如CGLIB代理模式下不能直接访问属性、final和private的方法不能代理等。