菜瓜:水稻,这次我特意去看了java的循环依赖

水稻:哟,有什么收获

菜瓜:两种情况,构造器循环依赖,属性循环依赖

  • 构造器循环依赖在逻辑层面无法通过。对象通过构造函数创建时如果需要创建另一个对象,就会存在递归调用。栈内存直接溢出
  • 属性循环依赖可以解决。在对象创建完成之后通过属性赋值操作。
  • package club.interview.base;
    
    /**
    * 构造器循环依赖 - Exception in thread "main" java.lang.StackOverflowError
    * toString()循环打印也会异常 - Exception in thread "main" java.lang.StackOverflowError
    * @author QuCheng on 2020/6/18.
    */
    public class Circular { class A {
    B b; // public A() {
    // b = new B();
    // } // @Override
    // public String toString() {
    // return "A{" +
    // "b=" + b +
    // '}';
    // }
    } class B {
    A a; // public B() {
    // a = new A();
    // } // @Override
    // public String toString() {
    // return "B{" +
    // "a=" + a +
    // '}';
    // }
    } private void test() {
    B b = new B();
    A a = new A();
    a.b = b;
    b.a = a;
    System.out.println(a);
    System.out.println(b);
    } public static void main(String[] args) {
    new Circular().test();
    }
    }

水稻:厉害啊,Spring也不支持构造函数的依赖注入,而且也不支持多例的循环依赖。同样的,它支持属性的依赖注入。

  • 看效果 - 如果toString()打印同样会出现栈内存溢出。
  • package com.vip.qc.circular;
    
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    /**
    * @author QuCheng on 2020/6/18.
    */
    @Component("a")
    public class CircularA { @Resource
    private CircularB circularB; // @Override
    // public String toString() {
    // return "CircularA{" +
    // "circularB=" + circularB +
    // '}';
    // }
    } package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
    * @author QuCheng on 2020/6/18.
    */
    @Component("b")
    public class CircularB { @Resource
    private CircularA circularA; // @Override
    // public String toString() {
    // return "CircularB{" +
    // "circularA=" + circularA +
    // '}';
    // }
    } @Test
    public void testCircular() {
    String basePackages = "com.vip.qc.circular";
    new AnnotationConfigApplicationContext(basePackages);
    }

菜瓜:看来spring的实现应该也是通过属性注入的吧

水稻:你说的对。先给思路和demo,之后带你扫一遍源码,follow me !

  • spring的思路是给已经初始化的bean标记状态,假设A依赖B,B依赖A,先创建A

    • 先从缓存容器(总共三层,一级拿不到就拿二级,二级拿不到就从三级缓存中拿正在创建的)中获取A,未获取到就执行创建逻辑
    • 对象A在创建完成还未将属性渲染完之前标记为正在创建中,放入三级缓存容器。渲染属性populateBean()会获取依赖的对象B。
    • 此时B会走一次getBean逻辑,B同样会先放入三级缓存,然后渲染属性,再次走getBean逻辑注入A,此时能从三级缓存中拿到A,并将A放入二级容器。B渲染完成放入一级容器
    • 回到A渲染B的方法populateBean(),拿到B之后能顺利执行完自己的创建过程。放入一级缓存
  • 为了证实结果,我把源码给改了一下,看结果

    • package com.vip.qc.circular;
      
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      
      /**
      * @author QuCheng on 2020/6/18.
      */
      @Component("a")
      public class CircularA { @Resource
      private CircularB circularB; @Override
      public String toString() {
      return "CircularA{" +
      "circularB=" + circularB +
      '}';
      }
      } package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
      * @author QuCheng on 2020/6/18.
      */
      @Component("b")
      public class CircularB { @Resource
      private CircularA circularA; @Override
      public String toString() {
      return "CircularB{" +
      "circularA=" + circularA +
      '}';
      }
      } 测试代码
      @Test
      public void testCircular() {
      String basePackages = "com.vip.qc.circular";
      new AnnotationConfigApplicationContext(basePackages);
      } 测试结果(我改过源码了)
      ----
      将a放入三级缓存
      将b放入三级缓存
      将a放入二级缓存
      将b放入一级缓存
      从二级缓存中拿到了a
      将a放入一级缓存

        

  • 再看源码
    • 关键类处理getSingleton逻辑 - 缓存容器

      • public class DefaultSingletonBeanRegistry 
        
          /** Cache of singleton objects: bean name to bean instance. */
        // 一级缓存
        private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */
        // 三级缓存
        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */
        // 二级缓存
        private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    • 主流程 AbstractApplicationContext#refresh() -> DefaultListableBeanFactory#preInstantiateSingletons() -> AbstractBeanFactory#getBean() & #doGetBean()
      • protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        /**
        * 处理FactoryBean接口名称转换 {@link BeanFactory#FACTORY_BEAN_PREFIX }
        */
        final String beanName = transformedBeanName(name);
        ...
        // ①从缓存中拿对象(如果对象正在创建中且被依赖注入,会放入二级缓存)
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
        ...
        }else {
        ...
        if (mbd.isSingleton()) {
        // ② 将创建的对象放入一级缓存
        sharedInstance = getSingleton(beanName, () -> {
        try {
        // ③ 具体创建的过程,每个bean创建完成之后都会放入三级缓存,然后渲染属性
        return createBean(beanName, mbd, args);
        }catch (BeansException ex) {
        ...
        ...
        return (T) bean;
        } 
    • ①getSingleton(beanName) - 二级缓存操作
      • protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 实例化已经完成了的放在singletonObjects
        Object singletonObject = this.singletonObjects.get(beanName);
        // 解决循环依赖
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
        singletonObject = singletonFactory.getObject();
        this.earlySingletonObjects.put(beanName, singletonObject);
        if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
        System.out.println("将"+beanName+"放入二级缓存");;
        this.singletonFactories.remove(beanName);
        }
        }else if(singletonObject != null){
        System.out.println("从二级缓存中拿到了"+beanName);
        }
        }
        }
        return singletonObject;
        }
    • ② getSingleton(beanName,lamdba) - 一级缓存操作
      • public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
        if (this.singletonsCurrentlyInDestruction) {
        ...
        // 正在创建的bean加入singletonsCurrentlyInCreation - 保证只有一个对象创建,阻断循环依赖
        beforeSingletonCreation(beanName);
        ...
        try {
        singletonObject = singletonFactory.getObject();
        ...
        finally {
        ...
        // 从singletonsCurrentlyInCreation中移除
        afterSingletonCreation(beanName);
        }
        if (newSingleton) {
        // 对象创建完毕 - 放入一级缓存(从其他缓存移除)
        addSingleton(beanName, singletonObject);
        }
        }
        return singletonObject;
        }
        } // ----- 内部调用一级缓存操作
        protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
        if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
        System.out.println("将"+beanName+"放入一级缓存");;
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
        }
        }
    • ③createBean(beanName, mbd, args) - 三级缓存操作
      • protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {
        ...
        if (instanceWrapper == null) {
        // 5* 实例化对象本身
        instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        ...
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
        ...
        // 将创建好还未渲染属性的bean 放入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        } Object exposedObject = bean;
        try {
        // 渲染bean自身和属性
        populateBean(beanName, mbd, instanceWrapper);
        // 实例化之后的后置处理 - init
        exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
        ...
        return exposedObject;
        } // ------------- 内部调用三级缓存操作
        protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
        if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
        System.out.println("将"+beanName+"放入三级缓存");;
        this.singletonFactories.put(beanName, singletonFactory);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
        }
        }
        }

菜瓜:demo比较简单,流程大致明白,源码我还需要斟酌一下,整体有了概念。这个流程好像是掺杂在bean的创建过程中,结合bean的生命周期整体理解可能会更深入一点

水稻:是的。每个知识点都不是单一的,拿着bean的生命周期再理解一遍可能会更有收获。

讨论

  • 为什么是三级缓存,两级不行吗?

    • 猜测:理论上两级也可以实现。多一个二级缓存可能是为了加快获取的速度。加入A依赖B,A依赖C,B依赖A,C依赖A,那么C在获取A的时候只需要从二级缓存中就能拿到A了

总结

  • Spring的处理方式和java处理的思想一致,构造器依赖本身是破坏语义和规范的
  • 属性赋值--> 依赖注入 。 先创建对象,再赋值属性,赋值的时候发现需要创建便生成依赖对象,被依赖对象需要前一个对象就从缓存容器中拿取即可

【spring】循环依赖 Java Vs Spring的更多相关文章

  1. spring循环依赖问题分析

    新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...

  2. Spring循环依赖

    Spring-bean的循环依赖以及解决方式 Spring里面Bean的生命周期和循环依赖问题 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环. ...

  3. Spring 循环依赖

    循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...

  4. Spring循环依赖的三种方式以及解决办法

    一. 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的 ...

  5. Spring循环依赖的解决

    ## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...

  6. 这个 Spring 循环依赖的坑,90% 以上的人都不知道

    1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...

  7. 烂大街的 Spring 循环依赖问题,你觉得自己会了吗

    文章已收录在 GitHub JavaKeeper ,N 线互联网开发.面试必备技能兵器谱,笔记自取. 微信搜「 JavaKeeper 」程序员成长充电站,互联网技术武道场.无套路领取 500+ 本电子 ...

  8. 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

    本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...

  9. 图解 Spring 循环依赖,写得太好了!

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

随机推荐

  1. golang如何优雅的编写事务代码

    目录 前言 需求 烂代码示例 重构套路 一.提前返回去除if嵌套 二.goto+label提取重复代码 三.封装try-catch统一捕获panic 前言 新手程序员概有如下特点 if嵌套特别多.重复 ...

  2. Java之预定义

    作为Java初学者的我,提供一个类似C#的预处理机制.若有不足之处,敬请各位大佬指正(感觉没有,哈哈哈哈哈哈)! Java 没有类似 C++的宏,也没有类似C#的预定义 #if...#endif C# ...

  3. Python中出现 SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 12-13: truncated \UXXXXXXXX escape

    Python中出现 SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 12-13: t ...

  4. 跟着阿里学JavaDay04——Java基础语法(二)

    一.Java运算符 运算符的简介 所有的程序开发,都是对数字的进行处理.而对数字的处理,一定会存在所谓的操作模式,而这些操作模式就被称为运算符. 算术运算符 对于算术运算符,我们主要掌握++,--的运 ...

  5. Docker 容器优雅终止方案

    原文链接:Docker 容器优雅终止方案 作为一名系统重启工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复 ...

  6. 2003 can't connect to mysql server

    在电脑中打开 计算机管理 点击 服务与应用程序 点击 服务 右侧找到 mysql 右键启动

  7. Java实现 LeetCode 744 寻找比目标字母大的最小字母(二分法)

    744. 寻找比目标字母大的最小字母 给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母. 在比较时,数组里字母的是循环有序的.举个例 ...

  8. Java动态规划实现最短路径问题

    问题描述 给定一个加权连通图(无向的或有向的),要求找出从每个定点到其他所有定点之间的最短路径以及最短路径的长度. 2.1 动态规划法原理简介 动态规划算法通常用于求解具有某种最优性质的问题.在这类问 ...

  9. Java实现 洛谷 P1980 计数问题

    import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = ...

  10. java算法集训结果填空题练习1

    1 空瓶换汽水 浪费可耻,节约光荣.饮料店节日搞活动:不用付费,用3个某饮料的空瓶就可以换一瓶该饮料.刚好小明前两天买了2瓶该饮料喝完了,瓶子还在.他耍了个小聪明,向老板借了一个空瓶,凑成3个,换了一 ...