前言
该文章是看视频与书籍,用个人的理解话语完成。所以此文不是很细致,仅是作为关键点记忆。
提示:以下是本篇文章正文内容,内容如有错误,可在评论区回复
一、什么是循环依赖
A对象里,有属性B对象。B对象里,有属性A对象。
A.B对象互相引用,创建时,产生的问题。
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
二、怎么解决问题
Spring通过提前暴露对象(即完成实例化但未完成初始化的对象)的方式解决循环依赖问题。使用三级缓存,map结构来存储对象。
- 先从三级缓存中获取A.发现没有后,创建A对象,进行实例化,生成了半成品A对象
- 在进行A对象属性填充前,将A对象的lamdba表达式存入三级缓存。
- populateBean设置属性时,需要B对象。容器中没有,开始创建B对象。从新走回第一步,这回创建的是B对象。
- B对象实例化后,设置属性前,将B对象的lamdba表达式存入三级缓存
- B开始设置属性,需要A对象,这时会从缓存中取A,先从一级缓存,二级缓存,三级缓存来取,三级缓存中有,将执行A的lamdba表达式getEarlyBeanReference,获得A的半成品状态。填充B的属性值。B已经是完整状态。
- 然后将A半成品存入二级缓存,将A的lamdba表达式从三级缓存移除。
- 将完整的B存入一级缓存,并且删除二,三级缓存中的B。
- 可以将B赋值给A对象了,将A存入一级,并且删除二,三级的A。
三级缓存
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/*
* 一级缓存
*/
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/*
* 三级缓存
* 用于保存BeanName和创建bean的工厂之间的关系
*/
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/*
* 二级缓存
* 保存BeanName和创建Bean实例之间的关系,与singletonFactories的不同之处在于,当一个单例
* Bean被放到这里之后,那么当bean还在创建过程中就可以通过getBean方法获取到,可以方便进行
* 循环依赖的检测
*/
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
.....
}
三、图解
其中有部分关键代码。可自行打断点调试。
四、面试题
1、一二三级缓存分别存放什么状态对象?
一级:成品对象二级:半成品三级:lamdba表达式
2、如果只设置一级缓存能否解决循环依赖问题?
如果只有一级缓存,成品对象和半成品都需要存入一级缓存,没办法区分状态。
3、如果只有二级缓存能否解决循环依赖问题?
能解决。但是前提是不使用代理。三级缓存是为了解决代理过程中的循环依赖问题。
思考,那些地方用到了三级缓存?
addSingletonFactory(),向三级缓存存放属性值getSingleton():从三级缓存中获取属性值
4、如果一个对象需要被代理的话,在整个容器中,会存在几个当前对象的版本?
有2个版本
1、原始对象,直接通过反射创建出的对象
2、通过cglib或jdk的动态代理创建对象来的对象
5、到底那里被代理了,当添加了aop之后,跟刚刚的处理步骤哪里不一样?exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);原始bean可能被修改,返回了被代理的对象。
6、总结:每次我们获取对象的时候,是通过对象的name来获取Bean的,如果原始对象和代理对象同时存在的话,那我通过名字在进行获取的时候,应该用哪一个?
无法选择,其实还有最核心的点,你如何能够确认对象时候需要被引用?
使用lamdba表达式其实代表了一种回调机制,当需要使用当前对象的时候,通过lamdba表达式来最终返回一个确定的最终版本对象,而不需要判断有几个对象,因为是替换的过程,所以只能有一个。
7、这三级缓存的查找顺序是啥?
先找singletonObjects,earlySingletonObjects,singletonFactories
总结
这里是完整内容
https://www.processon.com/view/link/62b31877e401fd071e0f6d37
参考内容:
- 《精通Spring+4.x++企业应用开发实战》
- B站上的Spring视频