关注△mikechen的互联网架构△,10年+BAT架构经验倾囊相授
大家好,我是 mikechen | 陈睿 。
Spring 循环依赖如何解决?是大厂面试高频。
本篇,我们重点详解 Spring 循环依赖解决。@mikechen
循环依赖
在探讨Spring循环依赖的解决方式以前,我们先来回忆一下什么是循环依赖。
循环依赖:就是多个bean之间相互依赖,形成了一个闭环。
比如:A依赖于B、B依赖于A。
如下图所示:
体现到代码中为:
@Component
public class A{
// 依赖B
@Autowired
private B b;
public B getB() {
return b;
}
}
@Component
public class B {
// 依赖A
@Autowired
private A a;
public A getA() {
return a;
}
}
Spring的循环依赖过程:
- 首先实例化A -> 属性填充注入B -> B还没有实例化;
- 需要先进行实例化B(A等待) -> 实例化B -> 注入A -> A实例化未完成,无法注入 -> 实例化B失败 -> 实例化A失败。
如此反复,就进入死循环了。
Spring如何解决循环依赖
下面,我还是用 A -> B -> A 的场景,我们按照过程一步步来分析,看一下Spring 是如何解决循环依赖的。
第一步:首先是实例化A
第二步:属性注入B
执行到属性填充环节需要注入B,因为Spring管理的bean默认是单例的,为防止重复创建Spring会先去容器中查找B,如果查找不到再进行创建。
如果Spring容器中是没有B,需要先实例化B,流程和实例化一致,如下图所示:
第三步:属性注入A
此时B也执行到属性填充的环节了,此时又需要注入A,此时还是会先去Spring容器中查找A,此时的A虽然没在单例池中,但是因为在创建中并且也在三级缓存中了。
所以此时获取A的流程就发生了变化,不再是直接创建,而是会从三级缓存中获取A,如下图所示:
三级缓存存放的并不是bean对象,而是生成bean的ObjectFactory,然后放入二级缓存中,同时返回A进行依赖注入。
第四步:初始化B
此时,继续执行B的实例化, 并将B从正在创建列表移出 , 将B放入一级缓存,同时将B在二级缓存和三级缓存中删,最后返回B。
在B实例化完成并返回后,A的实例化流程也从等待着苏醒继续执行,后续流程和B的完全一致。
然后整个流程:A -> B -> A的场景就结束了。
这样Spring通过三级缓存来解决循环依赖的,提前暴露的对象存放在三级缓存中,二级缓存存放过渡bean,一级缓存存放最终形态的bean。
Spring三级缓存
// 从上至下 分表代表这“三级缓存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
1.三级缓存(singletonFactories)
singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖。
2.二级缓存(earlySingletonObjects)
主要存放过渡bean,也就是三级缓存中ObjectFactory产生的对象。
提前曝光的单例对象的cache,存放原始的 bean 对象:尚未填充属性,用于解决循环依赖。
3.一级缓存(singletonObjects)
也被称为单例池,去存放已经创建完成,并且属性也注入完毕的对象,一般情况我们获取bean都是从这里获取的。
以上,是 Spring 循环依赖的详细解析,欢迎评论区留言交流或拓展。
我是 mikechen | 陈睿 ,关注【mikechen的互联网架构】,10年+BAT架构技术倾囊相授。
新的架构专题内容,第一时间更新至:阿里架构师进阶全部合集。
本文已同步我的技术博客 www.mikechen.cc,更新至我原创的《30W+字阿里架构技术合集》中。