为什么会出现循环依赖
其实这和Spring注入bean的流程相关的,bean的初始化是分为属性注入-》aware接口回调后再初始化的,所以这就导致如果两个类,比如A类和B类,A类中注入了B类,B类中又注入了A类的属性;
这个时候A这个Bean先被加载,属性注入的时候会去判断B是否被实例化,若是没有被实例化就要去实例化B,这时候的B是一个半成品的BeanDefinition;
然后接着B的属性注入,发现又依赖了A这个时候注入的是半成品的A;然后就完成了B的属性注入,接着实例化了B对象
紧接着再注入A中,A类再完成属性注入,实例化出A对象;其实Spring解决循环依赖,用了三级缓存;
- 一级缓存(singletonObjects)主要存放完成生命周期的Bean;
- 二级缓存(earlySingletonObjects)存放已经经过实例化或者经过aop的半成品的bean
- 三级缓存(singletonFactories)存放的是一个工厂,主要用于将Bean提前aop;
没有三级缓存可以解决循环依赖吗?
组合一:一级缓存 + 二级缓存
singletonObjects + earlySingletonObjects 理论可以解决依赖注入,也可以解决代理,但需要每次加入二级缓存都要是代理对象,如果没有代理就完全没有必要,同时也不符合 Spring 对 Bean 生命周期的定义。(对象都应该在创建之后再进行动态代理而不是单纯的实例化以后就急着进行代理,如果循环依赖就是没办法的事)
组合二:一级缓存 + 三级缓存
singletonObjects + singletonFactories 可以解决依赖注入的问题,但是没法解决代理的问题,若要进行代理从 ObjectFactory 中获取对象实例进行代理,但是这样每次获取对象都不是同一个
解决循环依赖的流程
1 | 创建 A |
Spring解决循环依赖时必须在“实例化之后、依赖注入之前”暴露 Bean?
因为不这样做没法解决循环依赖问题;实例化后的bean才能被引用,而如果在依赖注入之后才暴露 Bean,那么循环依赖已经发生,Spring 无法打破依赖闭环。
因此 Spring 在 Bean 实例化完成后、依赖注入之前通过三级缓存提前暴露 Bean 的 early reference,这样当其他 Bean 依赖该 Bean 时可以提前获取,从而解决循环依赖
3级缓存都放在堆里还是栈里?
堆里,因为栈里存的是:方法调用栈帧、局部变量、对象引用;而放在堆里是整个容器共享的,所有线程都可以访问
为什么 Spring 循环依赖必须是“字段注入或 setter 注入”,而构造器注入不行?
Spring 能解决setter/字段循环依赖,是因为Bean可以先实例化再注入依赖;而构造器循环依赖在实例化阶段就需要依赖对象,此时对象还不存在,因此无法打破循环