当前位置 : 主页 > 编程语言 > java >

一文帮你彻底打通【循环依赖】的任督二脉

来源:互联网 收集:自由互联 发布时间:2022-07-04
面试的重点,大厂必问之一: 循环依赖 1. 什么是循环依赖 看下图   上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,

一文帮你彻底打通【循环依赖】的任督二脉_面试题

面试的重点,大厂必问之一:

循环依赖

1. 什么是循环依赖

看下图

一文帮你彻底打通【循环依赖】的任督二脉_Spring_02

  上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。

2. 代码演示

  我们再通过代码的方式来演示下循环依赖的效果

public class CircularTest {

public static void main(String[] args) {
new CircularTest1();
}
}
class CircularTest1{
private CircularTest2 circularTest2 = new CircularTest2();
}

class CircularTest2{
private CircularTest1 circularTest1 = new CircularTest1();
}

执行后出现了 StackOverflowError 错误

一文帮你彻底打通【循环依赖】的任督二脉_java_03

  上面的就是最基本的循环依赖的场景,你需要我,我需要你,然后就报错了。而且上面的这种设计情况我们是没有办法解决的。那么针对这种场景我们应该要怎么设计呢?这个是关键!

3. 分析问题

  首先我们要明确一点就是如果这个对象A还没创建成功,在创建的过程中要依赖另一个对象B,而另一个对象B也是在创建中要依赖对象A,这种肯定是无解的,这时我们就要转换思路,我们先把A创建出来,但是还没有完成初始化操作,也就是这是一个半成品的对象,然后在赋值的时候先把A暴露出来,然后创建B,让B创建完成后找到暴露的A完成整体的实例化,这时再把B交给A完成A的后续操作,从而揭开了循环依赖的密码。也就是如下图:

一文帮你彻底打通【循环依赖】的任督二脉_java_04

4. 自己解决

  明白了上面的本质后,我们可以自己来尝试解决下:

先来把上面的案例改为set/get来依赖关联

public class CircularTest {

public static void main(String[] args) throws Exception{
System.out.println(getBean(CircularTest1.class).getCircularTest2());
System.out.println(getBean(CircularTest2.class).getCircularTest1());
}

private static <T> T getBean(Class<T> beanClass) throws Exception{
// 1.获取 实例对象
Object obj = beanClass.newInstance();
// 2.完成属性填充
Field[] declaredFields = obj.getClass().getDeclaredFields();
// 遍历处理
for (Field field : declaredFields) {
field.setAccessible(true); // 针对private修饰
// 获取成员变量 对应的类对象
Class<?> fieldClass = field.getType();
// 获取对应的 beanName
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 给成员变量赋值 如果 singletonObjects 中有半成品就获取,否则创建对象
field.set(obj,getBean(fieldClass));
}
return (T) obj;
}
}

class CircularTest1{
private CircularTest2 circularTest2;

public CircularTest2 getCircularTest2() {
return circularTest2;
}

public void setCircularTest2(CircularTest2 circularTest2) {
this.circularTest2 = circularTest2;
}
}

class CircularTest2{
private CircularTest1 circularTest1;

public CircularTest1 getCircularTest1() {
return circularTest1;
}

public void setCircularTest1(CircularTest1 circularTest1) {
this.circularTest1 = circularTest1;
}
}

然后我们再通过把对象实例化和成员变量赋值拆解开来处理。从而解决循环依赖的问题

public class CircularTest {
// 保存提前暴露的对象,也就是半成品的对象
private final static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();

public static void main(String[] args) throws Exception{
System.out.println(getBean(CircularTest1.class).getCircularTest2());
System.out.println(getBean(CircularTest2.class).getCircularTest1());
}

private static <T> T getBean(Class<T> beanClass) throws Exception{
//1.获取类对象对应的名称
String beanName = beanClass.getSimpleName().toLowerCase();
// 2.根据名称去 singletonObjects 中查看是否有半成品的对象
if(singletonObjects.containsKey(beanName)){
return (T) singletonObjects.get(beanName);
}
// 3. singletonObjects 没有半成品的对象,那么就反射实例化对象
Object obj = beanClass.newInstance();
// 还没有完整的创建完这个对象就把这个对象存储在了 singletonObjects中
singletonObjects.put(beanName,obj);
// 属性填充来补全对象
Field[] declaredFields = obj.getClass().getDeclaredFields();
// 遍历处理
for (Field field : declaredFields) {
field.setAccessible(true); // 针对private修饰
// 获取成员变量 对应的类对象
Class<?> fieldClass = field.getType();
// 获取对应的 beanName
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 给成员变量赋值 如果 singletonObjects 中有半成品就获取,否则创建对象
field.set(obj,singletonObjects.containsKey(fieldBeanName)?
singletonObjects.get(fieldBeanName):getBean(fieldClass));
}
return (T) obj;
}
}

class CircularTest1{
private CircularTest2 circularTest2;

public CircularTest2 getCircularTest2() {
return circularTest2;
}

public void setCircularTest2(CircularTest2 circularTest2) {
this.circularTest2 = circularTest2;
}
}

class CircularTest2{
private CircularTest1 circularTest1;

public CircularTest1 getCircularTest1() {
return circularTest1;
}

public void setCircularTest1(CircularTest1 circularTest1) {
this.circularTest1 = circularTest1;
}
}

运行程序你会发现问题完美的解决了

一文帮你彻底打通【循环依赖】的任督二脉_面试题_05

  在上面的方法中的核心是getBean方法,Test1 创建后填充属性时依赖Test2,那么就去创建 Test2,在创建 Test2 开始填充时发现依赖于 Test1,但此时 Test1 这个半成品对象已经存放在缓存到 ​​singletonObjects​​ 中了,所以Test2可以正常创建,在通过递归把 Test1 也创建完整了。

一文帮你彻底打通【循环依赖】的任督二脉_java_06

最后总结下该案例解决的本质:

一文帮你彻底打通【循环依赖】的任督二脉_面试题_07

5. Spring循环依赖

  然后我们再来看看Spring中是如何解决循环依赖问题的呢?刚刚上面的案例中的对象的生命周期的核心就两个

一文帮你彻底打通【循环依赖】的任督二脉_面试题_08

  而Spring创建Bean的生命周期中涉及到的方法就很多了。下面是简单列举了对应的方法

一文帮你彻底打通【循环依赖】的任督二脉_面试题_09

  基于前面案例的了解,我们知道肯定需要在调用构造方法方法创建完成后再暴露对象,在Spring中提供了三级缓存来处理这个事情,对应的处理节点如下图:

一文帮你彻底打通【循环依赖】的任督二脉_三级缓存_10

对应到源码中具体处理循环依赖的流程如下:

一文帮你彻底打通【循环依赖】的任督二脉_面试题_11

  上面就是在Spring的生命周期方法中和循环依赖出现相关的流程了。那么源码中的具体处理是怎么样的呢?我们继续往下面看。

首先在调用构造方法的后会放入到三级缓存中

一文帮你彻底打通【循环依赖】的任督二脉_循环依赖_12

下面就是放入三级缓存的逻辑

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 使用singletonObjects进行加锁,保证线程安全
synchronized (this.singletonObjects) {
// 如果单例对象的高速缓存【beam名称-bean实例】没有beanName的对象
if (!this.singletonObjects.containsKey(beanName)) {
// 将beanName,singletonFactory放到单例工厂的缓存【bean名称 - ObjectFactory】
this.singletonFactories.put(beanName, singletonFactory);
// 从早期单例对象的高速缓存【bean名称-bean实例】 移除beanName的相关缓存对象
this.earlySingletonObjects.remove(beanName);
// 将beanName添加已注册的单例集中
this.registeredSingletons.add(beanName);
}
}
}

然后在填充属性的时候会存入二级缓存中

earlySingletonObjects.put(beanName,bean);
registeredSingletons.add(beanName);

最后把创建的对象保存在了一级缓存中

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 将映射关系添加到单例对象的高速缓存中
this.singletonObjects.put(beanName, singletonObject);
// 移除beanName在单例工厂缓存中的数据
this.singletonFactories.remove(beanName);
// 移除beanName在早期单例对象的高速缓存的数据
this.earlySingletonObjects.remove(beanName);
// 将beanName添加到已注册的单例集中
this.registeredSingletons.add(beanName);
}
}

6. 疑问点

这些疑问点也是面试官喜欢问的问题点

为什么需要三级缓存

三级缓存主要处理的是AOP的代理对象,存储的是一个ObjectFactory

三级缓存考虑的是带你对象,而二级缓存考虑的是性能-从三级缓存的工厂里创建出对象,再扔到二级缓存(这样就不用每次都要从工厂里拿)

没有三级环境能解决吗?

没有三级缓存是可以解决循环依赖问题的

三级缓存分别什么作用

一级缓存:正式对象

二级缓存:半成品对象

三级缓存:工厂

一文帮你彻底打通【循环依赖】的任督二脉_三级缓存_13


【文章转自韩国多ip站群服务器 http://www.558idc.com/krzq.html处的文章,转载请说明出处】
上一篇:EXT学习之——Ext下拉框绑定以及级联写法
下一篇:没有了
网友评论