Spring是如何解决循环依赖的?

通过三级缓存提前暴露对象解决的。

三级缓存存放了哪些对象信息?

  • 一级缓存存放的是完整对象。
  • 二级缓存存放的是那些属性还没赋值的对象。
  • 三级缓存存放的是ObjectFactory<?>类型的lambda表达式,就是这用于处理AOP循环依赖的。

没有三级缓存,只有二级或一级也可以解决循环依赖

  spring要保证几个事情,只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将完整对象和属性没有赋值的对象分开,处理起来更加优雅、简单、易扩展。另外spring的两大特性中不仅有IOC还有AOP,就是基于字节码增强后的方法,存放在什么地方,而三级缓存最主要,要解决的循环依赖就是对AOP的处理,但如果把AOP代理对象的创建提前,那么二级缓存也一样可以解决,但是违背了String创建对象的原则,Spring更喜欢把所有的普通Bean都初始化完成,在处理代理对象的初始化。

什么是循环依赖?

  • 循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖
  • 无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
  • 所以Spring提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话需要如何解决
    问题展现
public class Test{public static void main(String [] args){new AClass();}
}public AClass{private BClass b = new BClass();
}public BClass{private AClass a = new AClass();
}
  • 这段代码就是循环依赖的初级样子,互相依赖,就会形成死循环的模式。运行就会报错java.lang.StackOverflowError
  • 这样的循环依赖代码是无法解决的,当你看到Spring中提供了get/set或者注解,这样之所以能解决,首先是进行了一定的解耦。让类的创建和属性的填充分离,先创建出半成品bean,再处理属性的填充,完成成品bean的提供。
    问题处理
    这部分核心代码就是解决循环依赖,演示如下
public class HandleTest{private final static Map<String, Object> singletonObject = new ConcurrentHashMap<>(256);public static void main(String[] args) throws Exception{System.out.println(getBean(B.class).getA());System.out.println(getBean(A.class).getB());}private static <T> T getBean(Class<T> beanClass) throws Exception{String beanName = beanClass.getSimpleName().toLowerCase();if(singletonObject.containsKey(beanName)){return (T) singletonObject.get(beanName);           }//实例化对象入缓存Object obj = beanClass.newInstance();singletonObject.put(beanName, obj);//属性填充补全对象Field[] fields = obj.getClass().getDeclaredFields();for(Field field : fields){field.setAccessible(true);Class<?> fieldClass = fieldClass.getSimpleName().toLowerCase();field.set(obj, singletonObject.containsKey(fieldBeanName) ? singletonObject.get(fieldBeanName) : getBean(fieldClass));field.setAccessible(false);}     return (T) obj;}
}class A{private B b;//...get/set
}class B{private A a;//...get/set
}
  • 这段代码提供了A、B两个类,互相有依赖。但在两个类中的依赖关系使用的是setter的方式进行填充。也就是只有这样才能避免两个类在创建之处不非得强依赖与另外一个对象。
  • getBean是整个解决循环依赖的核心内容。A创建后填充属性时依赖B,那么就去创建B,在创建B开始填充时发现依赖于A,但此时A这个半成品对象已经放在缓存到singletonObject中了,所以B可以正常创建,在通过递归把A也创建完成了。

源码分析

整个处理spring循环依赖过程:

Spring循环依赖对象获取过程,有这份执行流程调试起来就方便多了。
总结:

   1. 只有一级缓存并不能解决循环依赖。根据Spring中代码处理的流程,我们去分析一级缓存这样存放成品Bean的流程中,是不能解决循环依赖的问题的。因为A的成品创建依赖于B,B的成品创建又依赖于A,当需要补全B的属性时A还没有创建完,所以会出现死循环。
   2. 有了二级缓存其实这个事处理起来就会容易,一个缓存用于存放成品对象,另一个缓存用于存放半成品对象。A在创建半成品对象后存放到缓存中,接下来补充A对象中依赖B的属性。B继续创建,创建半成品同样放到缓存中,在补充对象的A属性时,可以从半成品缓存中获取,现在B就是一个完整的对象了,而接下来像是递归操作一样A也是一个完整的对象了。
   3. 有了二级缓存就可以解决Spring依赖了,三级缓存存在的意义是什么?三级缓存主要是解决Spring Aop的特性。AOP本身就是对方法的增强,是ObjectFactory<?>类型的almbda表达式,而Spring的原则又不希望将此类类型的Bean前置创建,所以要存放到三级缓存中处理。其实整体处理过程类似,唯独是B在填充属性A时,先查询成品缓存,再查询半成品缓存,最后在看看有没有单例工程类在三级缓存中。最终获取到以后调用getObject方法返回代理引用或者原始引用。至此也就解决了Spring AOP所带来的三级缓存问题。
共同探讨学习技术创建技术氛围Day9884125

Spring的getBean解决循环依赖相关推荐

  1. 互相引用 spring_听说你还不知道Spring是如何解决循环依赖问题的?

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

  2. Spring是如何解决循环依赖的

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够 ...

  3. 万字长文带你吃透Spring是怎样解决循环依赖的

    在Spring框架中,处理循环依赖一直是一个备受关注的话题.这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化.同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成 ...

  4. Spring 是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  5. Spring三级缓存解决循环依赖问题详解

    spring三级缓存解决循环依赖问题详解 前言 这段时间阅读了spring IOC部分的源码.在学习过程中,自己有遇到过很多很问题,在上网查阅资料的时候,发现很难找到一份比较全面的解答.现在自己刚学习 ...

  6. Spring是如何解决循环依赖的?

    1.案发情况 @Service public class TestService1 {@Autowiredprivate TestService2 testService2;@Asyncpublic ...

  7. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  8. Spring三级缓存解决循环依赖

    1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 其实可以N=1,也就 ...

  9. 【171期】面试官:小伙汁,Spring是怎么解决循环依赖的呢?

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 17 分钟. 来自:blog.csdn.net/Baisitao_/article/details/107349302 前言 ...

最新文章

  1. ubuntu下7z文件的解压方法
  2. 【MySQL】基础知识
  3. 《LeetCode力扣练习》第94题 二叉树的中序遍历 Java
  4. 用c#算成绩的总和_C# 基础知识系列- 6 Lambda表达式和Linq简单介绍
  5. python多图拼接并利用resnet提取特征
  6. 【技术系列】浅谈GPU虚拟化技术(第一章)
  7. unique函数详解
  8. 面试问到java并发_那些面试官必问的JAVA多线程和并发面试题及回答
  9. springdata jpa单表操作crud
  10. Git --- 傻瓜内容跟踪器
  11. 我最喜欢的Bash骇客
  12. 帮设计师打开思路的网站主页设计案例,给你出灵感
  13. 地图漫游功能的具体体现_【漫游天下】赣南 | 悠扬风韵之都
  14. MySQL外键约束_ON DELETE CASCADE/ON UPDATE CASCADE
  15. Android-JNI开发系列《五》局部引用全局引用全局弱引用缓存策略
  16. UNIX环境高级编程——标准I/O库缓冲区和内核缓冲区的区别
  17. heartbeat+lvs+Keepalive
  18. 为什么大多数程序员没有工程思维
  19. CReFF缓解长尾数据联邦学习(IJCAI 2022)
  20. lr_think_time()

热门文章

  1. 阿里云服务器ECS共享型和企业级是什么?
  2. 【计算机科学基础】加密算法
  3. mutations vuex 调用_vuex的mutations与actions的使用测试
  4. 计算机不能进入桌面,电脑进不了桌面怎么办 电脑进不了桌面解决方法
  5. mysql: however file don't exists. Create writable for user 'mysql'.
  6. 对tcp服务器最大连接数的理解
  7. 2021现代软件工程考试复习指南
  8. 【时空融合论文精读1】Fit-FC
  9. php调查问卷单选框标题,word调查问卷制作:怎样设置复选框各单选框
  10. 施密特正交化c语言,C语言实现矩阵的LU分解、施密特正交化、Givens分解、Householder分解...