菜瓜:水稻,这次我特意去看了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. Layui 解决动态图标不动的问题

    <i class="layui-icon layui-icon-face-smile" style="color: red; font-size: 100px;&q ...

  2. 在树莓派里搭建 Lighttpd 服务器

    Lighttpd 像 Ngnix 一样,是被设计运行在低内存,低 CPU 负载的设备上,它们都非常适合在树莓派上运行. 本文将介绍如何在树莓派上运行基本配置的 Lighttpd ,以及如何与 PHP- ...

  3. 【JavaScript数据结构系列】05-链表LinkedList

    [JavaScript数据结构系列]05-链表LinkedList 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识链表结构(单向链表) 链表也是线性结构, 节点相连构成链表 ...

  4. [Python基础]005.语法(4)

    语法(4) 类 创建 self 方法 变量 综合例子 继承 类 创建 与其他大部分语言一样,Python也用 class 来创建类. class Person: # Person类 pass # 空语 ...

  5. 看了这篇,我确定你已经彻底搞懂Java的继承了

    遇到认真的读者是作者的一种幸运,真的,上一篇接口推送后,有好几个读者留言说,"二哥,你有一处内容需要修正,应该是接口中不能有 private 和 protected 修饰的方法." ...

  6. 关于oauth安全

    白话认证流程 A)用户打开客户端以后,客户端要求用户给予授权.(比如说你登陆淘宝,通过QQ这个第三方来登录时,这个时候淘宝将你引导至QQ的认证服务器) B)用户同意给客户端授权.(这个时候在你手机上弹 ...

  7. Rocket - diplomacy - enumerateMask

    https://mp.weixin.qq.com/s/s3hr5JJX2_pwNgdu8WqV0Q   介绍enumerateMask的实现.(仅供理解,非严谨证明)   ​​   1. 基本定义   ...

  8. 超干货!为了让你彻底弄懂MySQL事务日志,我通宵肝出了这份图解!

    还记得刚上研究生的时候,导师常挂在嘴边的一句话,"科研的基础不过就是数据而已."如今看来,无论是人文社科,还是自然科学,或许都可在一定程度上看作是数据的科学. 倘若剥开研究领域的外 ...

  9. Java实现 蓝桥杯VIP 算法训练 数组查找及替换问题

    描述 给定某整数数组和某一整数b.要求删除数组中可以被b整除的所有元素,同时将该数组各元素按从小到大排序.如果数组元素数值在A到Z的ASCII之间,替换为对应字母.元素个数不超过100,b在1至100 ...

  10. Java实现 LeetCode 134 加油站

    134. 加油站 在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升. 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升 ...