Spring中的循环依赖问题
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,它是解决问题的关键。
流程梳理:
- 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
- 为 A 创建一个 Bean工厂,并放入到 singletonFactories 中。
- 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
- 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
- 为 B 创建一个 Bean工厂,并放入到 singletonFactories 中。
- 发现 B 需要注入 A 对象,此时在一级、二级未发现对象A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A还是一个半成品,并没有完成属性填充和执行初始化方法)
- 将对象 A 注入到对象 B 中。
- 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
- 对象 A 得到对象B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
- 对象 A完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A
流程图总结:
参考链接:
spring循环依赖与三级缓存
循环依赖及解决方法
五. 总结
整个从创建bean到解决循环依赖的过程:
context.getBean(A.class)->实例化->放入缓存->依赖注入B->getBean(B)->实例化B并放入缓存->B依赖注入A->getBean(A)获取到了缓存中的值并正常返回->B初始化成功->A初始化成功
Spring中的循环依赖问题相关推荐
- 面试:讲一讲Spring中的循环依赖
前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...
- 面试必杀技,讲一讲Spring中的循环依赖
本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...
- Spring中的循环依赖
目录 一.什么是循环依赖? 二.Bean的生命周期 2.1 Spring Bean 的生命周期 2.2 Bean 的生成步骤 三.三级缓存 3.1三个缓存分别有什么作用 四.思路分析 4.1 为什么 ...
- 一起来踩踩 Spring 中这个循环依赖的坑!
作者:Mythsman blog.mythsman.com/post/5d838c7c2db8a452e9b7082c/ 1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什 ...
- Spring中的循环依赖及解决,2021Java精选面试实战总结整理
那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这 ...
- Spring中的循环依赖解决详解
目录 1 什么是循环依赖? 1.1 构造器循环依赖 1.2 field属性注入循环依赖 1.3 field属性注入循环依赖(prototype) 2 循环依赖处理 2.1 构造器循环依赖(无法解决) ...
- 面试——Spring中的循环依赖
1 什么是Spring循环依赖 // A依赖了B,B是A对象中的一个属性 class A{public B b; }// B依赖了A class B{public A a; } 在普通的代码中,对象之 ...
- what?spring已经解决循环依赖了,为啥还报循环依赖错误?
前言 spring中的循环依赖及三层map解决方案,八股文中重灾区,强如小伙伴们或许一字一句倒背如流了,当年的我也是如此,然而现实狠狠给了我一巴掌,报错虽迟但到 报错一: The dependenci ...
- 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖
前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...
最新文章
- 重读【代码整洁之道】
- 插入排序--希尔排序
- springcloud 之 配置中心服务 spring cloud config
- R40 gpio 寄存器地址操作【原创】
- .NET在后置代码中输入JS提示语句(背景不会变白)
- 下载外网资源慢的解决办法
- 【实用开发工具】将BAT脚本打包成exe可执行文件
- 谷歌浏览器(1) : 主题
- Android手机打开开发者模式调试App
- 计算机网络存储设备有哪些,存储设备有哪些
- GPS定位(四)-经纬度格式转换-(互转 度转度分秒 度分秒转度……)
- 如何编制试算平衡表_会计实务:试算平衡表的编制步骤
- 分布式架构、大数据、机器学习、搜索、推荐、广告
- 白嫖我珍藏已久的在线小工具【第一弹】
- Java编程语言介绍
- 第 7 章 Database design E-R diagram
- ora-00942表或视图不存在,解决办法
- 【软件测试】航班订票系统测试
- 背诵微机原理与接口这一篇就够了
- 如何实时同步数据到StarRocks