1. 什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A我们直接上代码

先创建一个类ServiceA依赖于ServiceB,然后ServiceB又依赖于ServiceA

@Component
public class ServiceA { @Autowired
private ServiceB serviceb;
@Component
public class ServiceB { @Autowired
private ServiceA servicea;

2.Spring的单例对象的初始化主要分为三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

  2. 填充属性,这一步主要是多bean的依赖属性进行填充

  3. 调用实现了InitializingBean的接口,实现afterPropertiesSet方法,或者调用在配置文件中同过init-method的方法。

  4. 从上面单例bean的初始化可以知道:循环依赖主要发生在第一、二步,也就是构造器循环依赖和field循环依赖。那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

    这里会涉及到在spring内部所使用的两个内部属性,singletonFactories和earlySingletonObjects,这两个属性在类DefaultSingletonBeanRegistry中被定义,定义如下:

下面我们就看下spring创建单例bean的步骤吧

第一次创建ServiceA的步骤如下

org.springframework.beans.factory.support.AbstractBeanFactory#getBean方法在进入

由于是首次创建ServiceA在,singletonFactories和earlySingletonObjects和singletonFactories都不存在。所以返回了null。

然后通过反设创建了一个serviceA的BeanWrapper,然后在调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory这时候我们singletonFactories就存在serviceA了

紧接着就是调用populateBean填充ServiceA的属性了,由于ServiceA有属性ServiceB,此时我们的ServiceB还没有被加载,然后ServiceA有需要ServiceB于是spring又去调用了创建ServiceB的方法

于是我们接着继续填充ServiceB的属性,由于ServiceB又依赖ServiceA,于是再去创建ServiceA由于此时ServiceA已经在singletonFactories里面了,然后我们就返回了一个是ServiceA(此时的属性serviceB是为null的),并且ServiceA从singletonFactories里面移除掉加入到earlySingletonObjects。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

参考:https://blog.csdn.net/u010853261/article/details/77940767

Spring循环依赖解决方式源码解析的更多相关文章

  1. Spring里bean之间的循环依赖解决与源码解读

    通过前几节的分析,已经成功将bean实例化,但是大家一定要将bean的实例化和完成bean的创建区分开,bean的实例化仅仅是获得了bean的实例,该bean仍在继续创建之中,之后在该bean实例的基 ...

  2. Session获取不到的情况及解决办法(源码解析)

    本博客是自己在学习和工作途中的积累与总结,仅供自己参考,也欢迎大家转载,转载时请注明出处,请尊重他人努力成果,谢谢. 1. 当有连个sessionFactory时,容易产生获取不到session的情况 ...

  3. Spring AOP的实现及源码解析

    在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的. 想了解JDK的动态代理及反射的实现和源码分析,请参见下面 ...

  4. Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析

    1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...

  5. 建议收藏!利用Spring解决循环依赖,深入源码给你讲明白!

    前置知识 只有单例模式下的bean会通过三级缓存提前暴露来解决循环依赖的问题.而非单例的bean每次获取都会重新创建,并不会放入三级缓存,所以多实例的bean循环依赖问题不能解决. 首先需要明白处于各 ...

  6. SpringBoot 源码解析 (八)----- Spring Boot 精髓:事务源码解析

    本篇来讲一下SpringBoot是怎么自动开启事务的,我们先来回顾一下以前SSM中是如何使用事务的 SSM使用事务 导入JDBC依赖包 众所周知,凡是需要跟数据库打交道的,基本上都要添加jdbc的依赖 ...

  7. 【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)

    我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时 ...

  8. Spring MVC工作原理及源码解析(二)DispatcherServlet实现原理及源码解析

    1.DispatcherServlet 处理流程 从上一篇文章中Spring MVC原理图中我们可以看出:DispatcherServlet 在 Spring MVC框架 中处于核心位置,它负责协调和 ...

  9. 【spring源码学习】spring的事务管理的源码解析

    [一]spring事务管理(1)spring的事务管理,是基于aop动态代理实现的.对目标对象生成代理对象,加入事务管理的核心拦截器==>org.springframework.transact ...

随机推荐

  1. codeforces 1424J,为了过这题,我把祖传的C++都用上了!

    大家好,我们选择的是Bubble Cup比赛Div2场次的J题,不用问我Bubble Cup是什么比赛,我也不清楚.总之是一场算法比赛就是了.可能是这个比赛知名度比较低吧,参与的人数也不是很多,我们选 ...

  2. Java lambda 分组后多列求和

    主要思路是reducing,可以像sql一样分组后多列求和处理成新对象等: select code,max(name)as name,sum(chengJi)as chengJi,sum(age)as ...

  3. JAVA在最新版Windows10_1909版本环境下的环境变量配置

    1.配置 1.1新建 JAVA_HOME C:\Program Files\Java\jdk-13.0.2 1.2新建 CLASSPATH .;%JAVA_HOME%\bin;%JAVA_HOME%\ ...

  4. python MD5加密和flask-generate_password_hash

    实际开发过程中,有些数据是需要加密保存或者处理的,为了就是为了保证源数据的安全性.那么MD5加密作为一种简单有效的非对称加密方式在日常开发过程中也经常的被使用到.下面就来介绍下MD5算法: 1.  * ...

  5. MongoEngine模块

    今儿,接到老的的要求,要把新功能的数据存放在Mongo里.虽然一直都有听过Mango的鼎鼎大名,但是那也只是见过没用过,跟个小白没啥区别.在加上功能急需,没办法只能赶鸭子上架先上再说.下面这篇就是我赶 ...

  6. PyQt(Python+Qt)学习随笔:Qt Designer中部件的焦点策略focusPolicy设置

    在Qt Designer中可以设置部件的焦点策略,部件的焦点策略属性取值范围由枚举类型Qt.FocusPolicy来定义,该枚举类型及其含义如下表所示: 注意:经老猿测试鼠标轮滚动获取焦点,只有在鼠标 ...

  7. LoadRunner 多用户并发 登录,上传数据,登出的脚本教程

    这里记录 Web/Http  模式,模拟多用户并发进行  : 登录,上传数据,退出登录一整套流程.并发的用户量多少,可自定义.这里不介绍录屏的方式,是自己写脚本去执行的. 1.安装loadRunner ...

  8. sql绕过小技巧

    两个空格代替一个空格,用Tab代替空格,%a0=空格: %20 %09 %0a %0b %0c %0d %a0 %00 /**/ /*!*/ 最基本的绕过方法,用注释替换空格: /* 注释 */ 使用 ...

  9. 《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游

    本章通过跟踪hello程序的生命周期来开始对计算机系统进行学习.一个源程序从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止.我们将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念 ...

  10. C#清除HTML标签方法

    删除字符串中HTML标签代码 public static string ClearHTMLTags1(string HTML) { string[] Regexs ={ @"<scri ...