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

一文帮你彻底打通【循环依赖】的任督二脉
在这里插入图片描述

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

循环依赖

1. 什么是循环依赖

看下图

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

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

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 错误

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

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

3. 分析问题

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

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

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> 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> 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;
    }
}

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

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

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

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

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

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

5. Spring循环依赖

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

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

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

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

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

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

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

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

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

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

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

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

 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

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

没有三级环境能解决吗?

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

三级缓存分别什么作用

一级缓存:正式对象

二级缓存:半成品对象

三级缓存:工厂

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


原文始发于微信公众号(波哥带你学编程):一文帮你彻底打通【循环依赖】的任督二脉

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/58881.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!