要解决循环依赖,就要先了解Bean的声明周期
Bean
定义:被IOC容器管理的对象
依赖注入方式
-
构造器注入
优点:可以注入一个final修饰的对象;只会执行一次,不会被篡改;依赖的对象是完全初始化的
-
Setter注入
缺点:无法注入一个final修饰的对象;容易被篡改
-
注解注入
缺点:无法注入一个final修饰的对象;只适用于IOC容器
生命周期
- 实例化
- 属性赋值:set一些属性值
- 初始化:执行一些aware接口中的方法、initMethod`方法、前置处理、后置处理等方法。
- 销毁
Spring循环依赖
Spring解决循环依赖的关键是提前暴露未完全创建的Bean
三级缓存
- 一级缓存:存储完全初始化的Bean
- 二级缓存:存储动态代理(完成实例化,但未进行属性注入和初始化的Bean),防止多重循环依赖多次创建动态代理
- 三级缓存:存储函数式接口(把Bean实例和名字放到方法中)
- 不会立马调用
- 实现了Aop则创建动态代理,没有实现则返回Bean实例,存储到二级缓存中
不使用二级缓存:如果只有二级缓存,所有的Bean不管是否循环依赖,都会在实例化时创建动态代理,而正常的Bean是在初始化后才创建动态代理的,违背了Bean的生命周期。(破坏循环依赖,其实使用二级缓存就够了,但为了不违背Bean生命周期,就使用三级缓存)
例:A—>B B—>A
从容器中获取A对象,没有则创建Bean;
创建Bean时,在实例化阶段,会先从一级缓存中获取完全初始化的Bean,如果获取不到的话,再从二级缓存中获取,再获取不到,则实例化Bean,并将Bean对象和名称放到函数式接口中,存储到三级缓存中;
属性注入阶段,因为A依赖B,B不存在,需要创建,同上,在三级缓存中存储函数式接口。又因为B依赖A,依次判断各级缓存中是否存在,最后创建实例的代理对象存储到二级缓存中;
初始化完毕后,B对象创建完成,删除二级三级缓存,将Bean存储到一级缓存;
A属性注入完成后,再初始化。
Spring的三级缓存已经解决了循环依赖的问题,但当遇到以下情况时,还是会报错
- 全部使用构造器注入
- 自己注入自己
解决方案
-
重新设计
产生循环依赖的原因可能是没有把各个类的职能区分开。
-
Setter注入
全部使用构造器注入,在创建对象的时候就要求创建依赖对象了,而依赖对象又不能初始化。故可以一个使用setter注入,一个使用构造器注入
-
@Lazy延迟加载
在构造方法上加上@Lazy。构造A的时候,不会创建B,而是注入一个B的代理对象,B未被真正的初始化。等到A实例调用B的方法时,代理对象才会触发B真正初始化。