2026/1/11 16:12:18
网站建设
项目流程
黄渡网站建设,佛山科技网站建设,招商外包公司,甘肃交通工程建设监理有限公司网站最近看了点面试题#xff0c;发现Spring循环依赖#xff0c;一二三级缓存还是一个盲点#xff0c;估计很多人也是一样吧#xff0c;就专门查了资料了解了这部分内容#xff0c;希望给在这部分内容茫然的同仁们一点点启发#xff0c;先赞后看你必能学会#x1f44d;…最近看了点面试题发现Spring循环依赖一二三级缓存还是一个盲点估计很多人也是一样吧就专门查了资料了解了这部分内容希望给在这部分内容茫然的同仁们一点点启发先赞后看你必能学会~ ~ ~你有没有写过这样的代码两个类A和BA里要用到BB里又要用到A结果Spring启动时“啪”地抛了个BeanCurrentlyInCreationException告诉你“循环依赖了”别慌这事儿Spring其实早有预案——今天咱们就用最接地气的方式把这个“死锁”怎么破、三级缓存怎么玩掰开揉碎讲明白。一、先举个“生活化”的例子机器人组装厂的死锁危机想象你在开个机器人组装厂这就是Spring容器专门生产各种机器人Bean。每个机器人得按流程造先搭骨架实例化调构造函数→ 装零件填属性比如依赖其他机器人→ 测试出厂初始化调PostConstruct等方法→ 合格了进“成品仓库”一级缓存随时能领用。某天接了两个订单造A机器人和B机器人。A的说明书“我得装个B的核心零件才能干活”A依赖BB的说明书“我得装个A的能源核心才能启动”B依赖A工人开工了先造A搭好骨架A的“裸体”对象准备装零件时发现要B——B还没造呢转头造B搭好骨架B的“裸体”对象准备装零件时发现要A——A也没造完呢得A等BB等A俩机器人都卡在“等零件”这一步工厂差点停工。这就是循环依赖两个Bean互相指着对方说“你得先给我我才完整”结果谁都动不了。二、Spring的“救场神器”三级缓存是个啥厂长急中生智搞了个“半成品暂存系统”——这就是Spring大名鼎鼎的三级缓存。简单说就是给刚搭好骨架的机器人发张“预订券”谁急着用先领个“毛坯版”顶上等正式零件造好再替换。这个系统分三层对应DefaultSingletonBeanRegistry类里的三个Map缓存层级比喻说法真实身份类名存啥玩意儿一级缓存成品仓库singletonObjectsConcurrentHashMap完全造好的机器人成品Bean实例化装零件测试全搞定随时能领。二级缓存毛坯暂存处earlySingletonObjectsHashMap刚搭好骨架的“裸体”机器人早期对象或从三级缓存“兑换”来的毛坯可能带“贴膜”AOP代理。三级缓存工厂仓库预订券singletonFactoriesHashMap“预订券”ObjectFactory工厂对象凭券能现场领个毛坯机器人含贴膜逻辑。三、三级缓存咋破解死锁一步步看流程附“流程图”还是用A→B→A的例子咱们跟着工人师傅走一遍1. 造A实例化→ 发“预订券”进三级缓存 → 装零件时发现要B ↓ 2. 造B实例化→ 发“预订券”进三级缓存 → 装零件时发现要A ↓ 3. B找A成品库一级无→毛坯暂存处二级无→工厂仓库三级找到A的“预订券” ↓ 4. 拿A的券“兑换”工厂现场给A的毛坯裸体骨架要代理就贴膜→ 毛坯进二级缓存券从三级缓存删掉 ↓ 5. 把A的毛坯当零件装给B → B装完测试 → 送进成品库一级缓存 ↓ 6. 回头给A装零件去成品库领B → A装完测试 → 送进成品库一级缓存结果A和B都造好了死锁解开靠的就是“先领毛坯顶上再补零件”的思路。四、关键原理为啥三级缓存这么设计1. 为啥构造器注入会“死锁”如果用构造器注入比如A的构造函数必须传BB的构造函数必须传A那问题就大了造A得先有B造B得先有A——俩机器人连骨架都没搭起来实例化都没完成哪来的“预订券”进三级缓存这不就死锁了吗所以构造器注入的循环依赖Spring直接摆烂抛异常2. 为啥需要三级缓存两级不行吗假设只有“成品库”一级和“毛坯暂存处”二级造A时得先把A的毛坯放进二级缓存不然B找A时找不到但毛坯要不要用AOP代理比如加日志、事务如果A本来不需要代理提前放毛坯没问题但如果A需要代理放原始毛坯就错了应该用代理对象。三级缓存的聪明之处在于用“预订券”ObjectFactory延迟生成毛坯。只有真的发生循环依赖比如B急着要A才调用ObjectFactory.getObject()生成毛坯顺便判断要不要代理生成后放进二级缓存。这样既避免了“提前代理”的浪费又保证了代理的正确性。五、源码瞅一眼三级缓存的真实面目光说不练假把式咱们看段Spring源码DefaultSingletonBeanRegistry类感受下三级缓存的“物理形态”/* by yours.tools - online tools website : yours.tools/zh/tripledes.html */ // 一级缓存成品Beankey: bean名, value: 成品Bean private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存早期Bean毛坯key: bean名, value: 原始对象或代理对象 private final MapString, Object earlySingletonObjects new HashMap(16); // 三级缓存ObjectFactory工厂key: bean名, value: 生成早期引用的工厂 private final MapString, ObjectFactory? singletonFactories new HashMap(16);关键方法提前暴露“预订券”在Bean实例化后调完构造函数Spring会把ObjectFactory放进三级缓存代码在AbstractAutowireCapableBeanFactory.doCreateBean()里/* by yours.tools - online tools website : yours.tools/zh/tripledes.html */ // 实例化Bean后暴露早期引用工厂到三级缓存 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 把ObjectFactory放进三级缓存工厂逻辑是调用getEarlyBeanReference生成早期引用 addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean)); } // addSingletonFactory方法往三级缓存塞ObjectFactory protected void addSingletonFactory(String beanName, ObjectFactory? singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); // 三级缓存存工厂 this.earlySingletonObjects.remove(beanName); // 清二级缓存防止重复 this.registeredSingletons.add(beanName); } } }getEarlyBeanReference判断是否要“贴膜”AOP代理这个方法是生成早期引用的核心会检查Bean是否需要AOP代理比如被Transactional标注protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; // 遍历所有BeanPostProcessor处理早期引用比如AOP代理 if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject ibp.getEarlyBeanReference(exposedObject, beanName); // AOP代理在这儿生成 } } } return exposedObject; // 返回原始对象或代理对象 }六、咋避免循环依赖老司机的建议优先用构造器注入“排雷”构造器注入一报错你就知道“这儿有循环依赖得重构”倒逼你把代码解耦比如引入中间层Service。实在绕不开用Setter/字段注入Spring的三级缓存只认这种“实例化后装零件”的注入方式。加Lazy注解“缓兵之计”在构造器注入的某个依赖上加LazySpring会注入个“代理对象”相当于“提货单”等真用的时候再去领成品打破死锁。别用Prototype作用域每次new一个对象三级缓存根本帮不上忙循环依赖必炸。七、总结三级缓存的本质Spring的三级缓存singletonFactories→earlySingletonObjects→singletonObjects说白了就是“用空间换时间”提前暴露半成品毛坯让依赖方先用着等正式零件造好再替换。核心是用ObjectFactory工厂“延迟生成早期引用”顺便搞定AOP代理的坑。不过话说回来循环依赖能解决不代表应该出现——它往往是代码耦合太高的信号。理解了三级缓存的原理下次遇到循环依赖你不仅能知道“为啥报错”还能笑着跟同事说“来咱用Lazy或者重构一下别让机器人组装厂再停工啦”完❤️ 如果你喜欢这篇文章请点赞支持 同时欢迎关注我的博客获取更多精彩内容本文来自博客园作者佛祖让我来巡山转载请注明原文链接https://www.cnblogs.com/sun-10387834/p/19346114