Spring循环依赖解析

154 阅读4分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

  • 原理

    • Spring主要解决的方案就是 bean的实例化和bean的属性填充分离 再结合一个三级缓存 实现的。

      • 一级缓存:singletonObjects 。这个就是单例池,存放我们的已经完整构建好的bean。

      • 二级缓存:earlySingletonObjects。存放半成品的bean,只实例化,但是没有填充属性和执行初始化方法的bean。

      • 三级缓存:singletonFactories。

        • 这里面只会放提前引用:就是指那些创建过程中被引用的bean的引用。 只有AOP+循环依赖了,才会被提前引用。里面放的是ObjectFactory对象,通过该对象可以直接获取代理引用。
    • 流程(A和B循环依赖)

      • 获取bean的时候,会先从1->2->3 依次往下找。找不到就要创建。

      • 没有AOP情况下:(只需要二级缓存)

        • A 初步实例化,A实例先放入二级缓存中

        • 依赖B去创建B。B也初步实例化,此时需要A,则将A引用注入B。

        • B完成放入单例池中,回到A,A完成B注入,A将二级缓存移动到单例池中。

      • AOP情况下:(需要三级缓存)

        • A 初步实例化,会将A的一个ObjectFactory封装对象放入三级缓存中

        • 然后发现依赖B,去实例化B,B发现依赖A会从三个缓存中找到A,此时调用缓存中A的ObjectFactory方法创建A的代理对象(并非真的代理对象,而是看做提前曝光代理对象-没有被AOP增强的代理),将其放入二级缓存中。然后A对象注入B。将B放入单例池。

        • 回到A,将B注入A,然后A接着放入单例池。(放入单例池会删除他们之前的缓存)

  • 为什么要三级缓存

    • 一级缓存肯定不行:创建A的时候,需要B,缓存没有就要创建B,创建B的时候又要A,A此时也没有在缓存里面,就再创建A而创建B,就死循环了。

    • 二级缓存是可以的:

      • 如果没有AOP的场景,那么直接二级缓存就是够用的。

      • 而AOP场景下,创建B的时候,A放入的是原始对象,而不是代理对象!这样B将注入A的原始对象,而不是代理对象。

      • 提问:如果有AOP的场景,我们照理说在二级池中放入代理对象(可以完全在put的时候,直接调用getObject放入二级缓存啊),不就可以解决了吗?是的,可以的。但是不规范。

    • 三级缓存

      • 为啥一定要三级缓存?

        • Spring对bean的生命周期设计的原则是希望Bean初始化完成,再为其创建代理对象的。(毕竟代理最好代理最后完整的对象)

        • 所以A的代理真正实现应该是A执行初始化方法之后,执行后置处理器去创建A的代理对象。

        • 但是因为循环依赖的问题,需要提前创建A的代理对象。所以Spring提供了一个提前曝光的概念。通过getObject可以获得一个提前曝光的代理对象。当B完成后,A则可以继续通过后置处理器创建出最终的代理对象。

        • 其实也是一个程序设计上问题:spring可能希望二级缓存保留原有的正常功能,针对AOP特殊处理希望不要污染到二级缓存。如上我们走二级缓存的情况下,是没办法拿到代理对象的。所以添加一个缓存专门用来处理这个事情。

        • 我们其实也可以从另外一个角度看:将ObjectFactory的map看做二级,我们默认都会将bean的工厂缓存进这个map,因为缓存工厂用利于我们基于BeanPostProcessor进行扩展。但是如果只有这2个缓存的话,比如A依赖B和C,B和C都依赖A,那么每次获取A的时候都要getObject方法,并且它们返回的对象是不一样的。所以要加一个缓存,将这些getObject的先缓存住。这个getObject不一定都是代理对象!(这个答案更靠谱些)

  • 支持度

    • 原型模式(非单例)是不支持循环依赖的。(我也不知道你要新对象,还是老对象,新对象就会死循环)

    • 不支持基于构造器的循环依赖。(先有鸡还是先有蛋的问题)