Spring的的循环依赖问题

文章目录

  • Spring的的循环依赖问题
    • 一. 简介
      • 1.什么是循环依赖问题?
      • 2.循环依赖有什么影响?
    • 二. 循环依赖复现
    • 三. 解决方案
      • 1. 重新设计
      • 2 使用 @Lazy
      • 3. 使用setter注入
    • 四. 三级缓存
    • 五. 总结

一. 简介

1.什么是循环依赖问题?

类A依赖类B,类B也依赖类A,这种情况就会出现循环依赖。 Bean A → Bean B → Bean A

2.循环依赖有什么影响?

循环依赖会导致内存溢出

public class AService {private BService bService = new BService();
}public class BService {private AService aService = new AService();
}

当你通过 new AService() 创建一个对象时你会获得一个栈溢出的错误。 如果你了解 Java的初始化顺序就应该知道为什么会出现这样的问题。

因为调用 new AService() 时会先去执行属性 bService 的初始化, 而 bService 的初始化又会去执行AService 的初始化, 这样就形成了一个循环调用,最终导致调用栈内存溢出。

二. 循环依赖复现

StudentA 依赖StudentB,同时 StudentB也依赖StudentA

@Component
public class StudentA {private String nameA;@Autowiredprivate StudentB studentB;public StudentA( StudentB studentB) {this.studentB = studentB;}}
@Component
public class StudentB {private String nameB;@Autowiredprivate StudentA studentA;public StudentB( StudentA studengA) {this.studentA = studentA;}}

启动工程,我们会看到如下报错,这就是循环依赖导致的程序运行问题

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-11-07 13:47:13.714 ERROR 12744 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : ***************************
APPLICATION FAILED TO START
***************************Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
|  studentA defined in file [D:\prometheus\student-server\target\classes\com\student\studentserver\entity\xunhaunyilai\StudentA.class]
↑     ↓
|  studentB defined in file [D:\prometheus\student-server\target\classes\com\student\studentserver\entity\xunhaunyilai\StudentB.class]
└─────┘

三. 解决方案

1. 重新设计

当出现这种循环依赖时,很可能是在设计方面存在问题,没有把每个类的职责很好的区分开。应该尝试正确重新设计组件,以便其层次结构设计良好,并且不需要循环依赖项。‎如果无法重新设计,那么可以考虑其他解决办法。

2 使用 @Lazy

解决Spring循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。

@Component
public class StudentA {private String nameA;@Autowiredprivate StudentB studentB;public StudentA(@Lazy StudentB studentB) {this.studentB = studentB;}}
@Component
public class StudentA {private String nameA;@Autowiredprivate StudentB studentB;public StudentA(@Lazy StudentB studengB) {this.studentB = studengB;}}

加上@Lazy以后,启动工程后就不报错了

3. 使用setter注入

@Component
public class StudentA {@Autowiredprivate StudentB studentB;public void testA(){System.out.println("student1");}
}
@Component
public class StudentB {@Autowiredprivate StudentA studengA;public void testB(){System.out.println("studentB");}
}

这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了(内部通过三级缓存进行解决)。

四. 三级缓存

三级缓存其实它更像是Spring容器工厂的内的术语,采用三级缓存模式来解决循环依赖问题,这三级缓存分别指

  • singletonObjects(一级缓存):用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
  • earlySingletonObjects(二级缓存):提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
  • singletonFactories(三级缓存):单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖(提前暴露)
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {...// 从上至下 分表代表这“三级缓存”private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存.../** Names of beans that are currently in creation. */// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~// 它在Bean开始创建时放值,创建完成时会将其移出~private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));/** Names of beans that have already been created at least once. */// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复// 至少被创建了一次的  都会放进这里~~~~private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)
加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

getSingleton()从缓存里获取单例对象步骤分析可知,Spring解决循环依赖的诀窍:就在于singletonFactories这个三级缓存。这个Cache里面都是ObjectFactory,它是解决问题的关键。

流程梳理

  1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
  2. 为 A 创建一个 Bean工厂,并放入到 singletonFactories 中。
  3. 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
  4. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
  5. 为 B 创建一个 Bean工厂,并放入到 singletonFactories 中。
  6. 发现 B 需要注入 A 对象,此时在一级、二级未发现对象A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A还是一个半成品,并没有完成属性填充和执行初始化方法)
  7. 将对象 A 注入到对象 B 中。
  8. 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
  9. 对象 A 得到对象B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
  10. 对象 A完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A

流程图总结:

参考链接:
spring循环依赖与三级缓存
循环依赖及解决方法

五. 总结

整个从创建bean到解决循环依赖的过程:

context.getBean(A.class)->实例化->放入缓存->依赖注入B->getBean(B)->实例化B并放入缓存->B依赖注入A->getBean(A)获取到了缓存中的值并正常返回->B初始化成功->A初始化成功

Spring中的循环依赖问题相关推荐

  1. 面试:讲一讲Spring中的循环依赖

    前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...

  2. 面试必杀技,讲一讲Spring中的循环依赖

    本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...

  3. Spring中的循环依赖

    目录 一.什么是循环依赖? 二.Bean的生命周期 2.1 Spring Bean 的生命周期 2.2 Bean 的生成步骤 三.三级缓存 3.1三个缓存分别有什么作用 四.思路分析 4.1 为什么 ...

  4. 一起来踩踩 Spring 中这个循环依赖的坑!

    作者:Mythsman blog.mythsman.com/post/5d838c7c2db8a452e9b7082c/ 1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什 ...

  5. Spring中的循环依赖及解决,2021Java精选面试实战总结整理

    那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这 ...

  6. Spring中的循环依赖解决详解

    目录 1 什么是循环依赖? 1.1 构造器循环依赖 1.2 field属性注入循环依赖 1.3 field属性注入循环依赖(prototype) 2 循环依赖处理 2.1 构造器循环依赖(无法解决) ...

  7. 面试——Spring中的循环依赖

    1 什么是Spring循环依赖 // A依赖了B,B是A对象中的一个属性 class A{public B b; }// B依赖了A class B{public A a; } 在普通的代码中,对象之 ...

  8. what?spring已经解决循环依赖了,为啥还报循环依赖错误?

    前言 spring中的循环依赖及三层map解决方案,八股文中重灾区,强如小伙伴们或许一字一句倒背如流了,当年的我也是如此,然而现实狠狠给了我一巴掌,报错虽迟但到 报错一: The dependenci ...

  9. 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖

    前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...

最新文章

  1. 重读【代码整洁之道】
  2. 插入排序--希尔排序
  3. springcloud 之 配置中心服务 spring cloud config
  4. R40 gpio 寄存器地址操作【原创】
  5. .NET在后置代码中输入JS提示语句(背景不会变白)
  6. 下载外网资源慢的解决办法
  7. 【实用开发工具】将BAT脚本打包成exe可执行文件
  8. 谷歌浏览器(1) : 主题
  9. Android手机打开开发者模式调试App
  10. 计算机网络存储设备有哪些,存储设备有哪些
  11. GPS定位(四)-经纬度格式转换-(互转 度转度分秒 度分秒转度……)
  12. 如何编制试算平衡表_会计实务:试算平衡表的编制步骤
  13. 分布式架构、大数据、机器学习、搜索、推荐、广告
  14. 白嫖我珍藏已久的在线小工具【第一弹】
  15. Java编程语言介绍
  16. 第 7 章 Database design E-R diagram
  17. ora-00942表或视图不存在,解决办法
  18. 【软件测试】航班订票系统测试
  19. 背诵微机原理与接口这一篇就够了
  20. 如何实时同步数据到StarRocks

热门文章

  1. vim !!_让我们学习Vim! 第2部分
  2. jdk8中List、map、set间转换(含转Map分组),以及List去重
  3. Android -- 下拉列表、列表视图、网格视图
  4. VSCode -> Writting snippet
  5. 作为程序员必须知道的编程语言编年史
  6. 扫描名片 识别名片 云脉名片识别
  7. linux计算生日时间,生日计算
  8. Process returned -1073741819 (0xC0000005)
  9. Window打开服务的cmd命令
  10. 大多数人都是如何挥霍掉自己的天赋?