希望之光永远向着目标清晰的人敞开。

1. 循环依赖概述

循环依赖通俗讲就是循环引用,指两个或两个以上对象的bean相互引用对方,A依赖于B,B依赖于A,最终形成一个闭环。

Spring循环依赖的场景有两种:

  • 构造器的循环依赖
  • field 属性的循环依赖

    对于构造器的循环依赖,Spring 是无法解决,只能抛出 BeanCurrentlyInCreationException 异常;对于field 属性的循环依赖,Spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。下面重点分析属性依赖的情况。

2. 循环依赖执行流程



以上流程图针对如下代码进行演示:

  1. @Component
  2. public class CircularRefA {
  3. public CircularRefA() {
  4. System.out.println("============CircularRefA()===========");
  5. }
  6. //这里会触发CircularRefB类型的getBean操作
  7. @Autowired
  8. private CircularRefB circularRefB;
  9. }
  1. @Component
  2. public class CircularRefB {
  3. public CircularRefB() {
  4. System.out.println("============CircularRefB()===========");
  5. }
  6. //又会触发A的getBean操作
  7. @Autowired
  8. private CircularRefA circularRefA;
  9. }

step1: A类实例化执行,第一次三个缓存中都没有,会走doGetBean一路走到createBeanInstance,完成无参构造函数实例化,并在addSingletonFactory中设置三级缓存;

step2:A类populateBean进行依赖注入,随后触发了B类属性的getBean操作;

step3: B类与A类类似,第一次三个缓存中也都没有,无参构造函数实例化后,设置三级缓存,将自己加入三级缓存;

step4:B类populateBean进行依赖注入,这里触发了A类属性的getBean操作;

step5: A类之前正在创建,此时已经是第二次进入,由于一级二级缓存中都没有,会从三级缓存中获取,并且允许 bean提前暴露则从三级缓存中拿到对象工厂,从工厂中拿到对象成功后,升级到二级缓存,并删除三级缓存;若以后有别的类引用的话就从二级缓存中进行取;

step6:B类拿到了A的提前暴露实例注入到A类属性中了,此时完成B类的实例化;

step7:A类之前依赖B类,B的实例化完成,进而促使A的实例化也完成,并且此时A的类B属性已经有值,A类继续走后续的afterSingletonCreateion与addSingleton方法,删除正在创建缓存中的实例,并将实例从二级缓存移入以及缓存,同时删除二三级缓存;

以上是A类实例化的全过程,下面会针对源码逐一分析。

3. 源码分析

首先创建A的实例,需要从A的getBean方法开始,到doGetBean,第一次优先从缓存中取,进入getSingleton方法:

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. // 从一级缓存singletonObjects中取
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. // 一级缓存为空,且单例对象正在创建
  5. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  6. synchronized (this.singletonObjects) {
  7. // 从二级缓存earlySingletonObjects中取
  8. singletonObject = this.earlySingletonObjects.get(beanName);
  9. // 如果二级缓存也为空,且允许bean提前暴露
  10. if (singletonObject == null && allowEarlyReference) {
  11. // 从三级缓存singletonFactories中取
  12. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  13. // 如果三级缓存不为空
  14. if (singletonFactory != null) {
  15. // 调用getEarlyBeanReference方法提前暴露bean
  16. singletonObject = singletonFactory.getObject();
  17. // 提前暴露的bean放入二级缓存,
  18. this.earlySingletonObjects.put(beanName, singletonObject);
  19. // 将提前暴露的bean从三级缓存中删除
  20. this.singletonFactories.remove(beanName);
  21. }
  22. }
  23. }
  24. }
  25. return singletonObject;
  26. }

这个方法主要是从三个缓存中获取,分别是:singletonObjectsearlySingletonObjectssingletonFactories,三者定义如下:

  1. /** Cache of singleton objects: bean name to bean instance. */
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. /** Cache of singleton factories: bean name to ObjectFactory. */
  4. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  5. /** Cache of early singleton objects: bean name to bean instance. */
  6. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

意义如下:

  • singletonObjects:单例对象的cache
  • singletonFactories : 单例对象工厂的cache
  • earlySingletonObjects :提前暴露的单例对象的Cache

解决循环依赖的关键是三个缓存,其中一级缓存singletonObjects存放完全实例化的对象,对象以及其依赖的属性都有值;二级缓存earlySingletonObjects存放半实例化的对象,相当于在内存开辟了空间,已完成创建,但是还未进行属性赋值,可以提前暴露使用;三级缓存singletonFactories为对象工厂,用来创建提前暴露的bean并放入二级缓存中。

首次初始化A时,三个缓存中都没有对象,会进入如下getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,该方法第二个参数是个函数式接口,当内部调用getObject方法时,会调用createBean方法:

  1. // 创建bean实例
  2. if (mbd.isSingleton()) {
  3. sharedInstance = getSingleton(beanName, () -> {
  4. try {
  5. return createBean(beanName, mbd, args);
  6. }
  7. catch (BeansException ex) {
  8. // Explicitly remove instance from singleton cache: It might have been put there
  9. // eagerly by the creation process, to allow for circular reference resolution.
  10. // Also remove any beans that received a temporary reference to the bean.
  11. destroySingleton(beanName);
  12. throw ex;
  13. }
  14. });
  15. ......

先进入getSingleton方法:

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(beanName, "Bean name must not be null");
  3. synchronized (this.singletonObjects) {
  4. Object singletonObject = this.singletonObjects.get(beanName);
  5. if (singletonObject == null) {
  6. if (this.singletonsCurrentlyInDestruction) {
  7. throw new BeanCreationNotAllowedException(beanName,
  8. "Singleton bean creation not allowed while singletons of this factory are in destruction " +
  9. "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
  10. }
  11. if (logger.isDebugEnabled()) {
  12. logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
  13. }
  14. // bean单例创建前
  15. beforeSingletonCreation(beanName);
  16. boolean newSingleton = false;
  17. boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
  18. if (recordSuppressedExceptions) {
  19. this.suppressedExceptions = new LinkedHashSet<>();
  20. }
  21. try {
  22. // 调用createBean方法创建bean
  23. singletonObject = singletonFactory.getObject();
  24. newSingleton = true;
  25. }
  26. catch (IllegalStateException ex) {
  27. // Has the singleton object implicitly appeared in the meantime ->
  28. // if yes, proceed with it since the exception indicates that state.
  29. singletonObject = this.singletonObjects.get(beanName);
  30. if (singletonObject == null) {
  31. throw ex;
  32. }
  33. }
  34. catch (BeanCreationException ex) {
  35. if (recordSuppressedExceptions) {
  36. for (Exception suppressedException : this.suppressedExceptions) {
  37. ex.addRelatedCause(suppressedException);
  38. }
  39. }
  40. throw ex;
  41. }
  42. finally {
  43. if (recordSuppressedExceptions) {
  44. this.suppressedExceptions = null;
  45. }
  46. // 创建完成后要从正在实例化的bean集合singletonsCurrentlyInCreation中删除该bean
  47. afterSingletonCreation(beanName);
  48. }
  49. if (newSingleton) {
  50. // bean加入缓存
  51. addSingleton(beanName, singletonObject);
  52. }
  53. }
  54. return singletonObject;
  55. }
  56. }

该方法完成流程图中的四个步骤:

step1:bean单例创建前,将beanName放入singletonsCurrentlyInCreation缓存;

step2: singletonFactory.getObject()调用外面函数式接口中的createBean方法创建bean;

step3afterSingletonCreation方法将beanName从singletonsCurrentlyInCreation缓存删除,表示已创建完;

step4: 实例化后加入一级缓存,二三级缓存删除。

以上1、3、4涉及代码如下:

  1. protected void beforeSingletonCreation(String beanName) {
  2. // 将正在创建的bean放入缓存singletonsCurrentlyInCreation
  3. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  4. throw new BeanCurrentlyInCreationException(beanName);
  5. }
  6. }
  1. protected void afterSingletonCreation(String beanName) {
  2. // 正在创建中的缓存容器singletonsCurrentlyInCreation清除刚刚创建的bean
  3. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
  4. throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
  5. }
  6. }
  1. protected void addSingleton(String beanName, Object singletonObject) {
  2. synchronized (this.singletonObjects) {
  3. // 一级缓存存放bean
  4. this.singletonObjects.put(beanName, singletonObject);
  5. // 三级缓存移除bean
  6. this.singletonFactories.remove(beanName);
  7. // 二级缓存移除bean
  8. this.earlySingletonObjects.remove(beanName);
  9. //
  10. this.registeredSingletons.add(beanName);
  11. }
  12. }

其中step2流程较长,当A第一次实例化时,走到创建无参构造函数实例化createBeanInstance,随后会走addSingletonFactory方法:

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3. synchronized (this.singletonObjects) {
  4. if (!this.singletonObjects.containsKey(beanName)) {
  5. this.singletonFactories.put(beanName, singletonFactory);
  6. this.earlySingletonObjects.remove(beanName);
  7. this.registeredSingletons.add(beanName);
  8. }
  9. }
  10. }

从这段代码我们可以看出singletonFactories这个三级缓存才是解决循环依赖的关键,该代码在createBeanInstance方法之后,也就是说这个bean其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了,所以Spring在这个时候选择将该对象提前曝光出来让大家认识认识。当三级缓存有值后,后面如果再次用到该bean的时候,会从三级缓存中,并通过提前暴露,升级到二级缓存中,到这里我们发现三级缓存singletonFactories和二级缓存earlySingletonObjects中的值都有出处了,那一级缓存在哪里设置的呢?就是在A创建完,并把A依赖的属性B也创建完后,B有依赖于A,再次进入A后,A直接从二级缓存中获取,从而促使B对象创建完,随即A也就创建完成,A完成createBean后走上面的step4中的addSingletion方法,完成一级缓存的设置。

4.总结

Spring在创建bean的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中),这样一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 getObject() 获取了,故在缓存中使用三级缓存获取到实例,并将实例升级到二级缓存,供后续实例如需二次使用时,可直接从二级缓存中取,待实例完全创建后,升级到一级缓存,并清理二级三级缓存,总而言之提前暴露三级缓存,以及一二三级缓存的综合使用是解决循环依赖的关键,各级缓存各司其职,又能够相互呼应,spring的设计实在精妙,给我们自己设计项目提供了一种优秀的思考方式。

【spring源码系列】之【Bean的循环依赖】的更多相关文章

  1. Spring源码系列(二)--bean组件的源码分析

    简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...

  2. Spring源码分析(十七)循环依赖

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决, ...

  3. Spring源码解析(五)循环依赖问题

    引言 循环依赖就是多个类之间互相依赖,比如A依赖B,B也依赖A,如果日常开发中我们用new的方式创建对象,这种循环依赖就会导致不断的在创建对象,导致内存溢出. Spring是怎么解决循环依赖的问题的? ...

  4. spring源码深度解析— IOC 之 循环依赖处理

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

  5. spring源码阅读笔记09:循环依赖

    前面的文章一直在研究Spring创建Bean的整个过程,创建一个bean是一个非常复杂的过程,而其中最难以理解的就是对循环依赖的处理,本文就来研究一下spring是如何处理循环依赖的. 1. 什么是循 ...

  6. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  7. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  8. Spring源码系列(四)--spring-aop是如何设计的

    简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...

  9. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

  10. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

随机推荐

  1. mybatis学习——日志工厂

    为什么要使用日志工厂? 我们想一下,我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?答案是肯定的,如果一个 数据库相关的操作出现了问题,我们就可以根据输出的S ...

  2. OpenFeign远程调用原理

    之前对OpenFeign 了解到只用在接口上面打个注解,然后就可以通过内部调用去调用远程地址.研究完Feign生成对象以及代理对象的作用过程之后发现这个过程用到了Spring的好多东西,在之后的过程中 ...

  3. 学习响应式编程 Reactor (2) - 初识 reactor

    Reactor Reactor 是用于 Java 的异步非阻塞响应式编程框架,同时具备背压控制的能力.它与 Java 8 函数式 Api 直接集成,比如 分为CompletableFuture.Str ...

  4. 多图:一文带你入门掌握JVM所有知识点

    本JVM系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握JVM相关的知识要点,难免会有所侧重,若想要更加系统更加详细的学习JVM知识,还是需要去阅读专业的书籍和文档. 本文主题内容: ...

  5. SQL中的分组之后TOPN问题

    SQL分组查询然后取每一组的前N条数据 由于SQL的不同的数据库SQL的语法有些略微不同,所以我们这里采用MySQL展示. 创建表 create table person(   id         ...

  6. 有效Ajax案例

    <script>$(document).ready(function(){ $("input:submit").click(function(){ $.ajax({ t ...

  7. Android系统编程入门系列之应用初始化Application

    在上一篇文章中我们了解到Android系统启动应用的时候,会首先加载AndroidManifest.xml清单文件中的一系列信息,在清单文件中如果不指定<application></ ...

  8. 学习Qt Charts - 不使用UI的情况下使用QTCharts

    新建一个Qt Widgets Application项目,不添加UI文件,如下图: 建立工程后,在.pro文件中添加: QT += charts 然后在.h文件中添加: #include " ...

  9. js笔记18

    1.面向对象 (1)单例模式 (2)工厂模式 (3)构造函数 a.类  js天生自带的类 基类   object 子类   Function  Array Number  Math  Boolean ...

  10. 10、nginx+uwsgi+django部署(动静分离)

    10.1.说明: 1.介绍: 创建Django项目,可以通过 pyhon3 manage.py runserver 0.0.0.0:8080 & 命令更方便地调试程序,但是如果当一个项目完成了 ...