当前位置: 首页 > news >正文

Spring如何巧妙解决循环依赖问题

什么是循环依赖?

循环依赖是指两个或多个Bean之间相互依赖,形成闭环的情况。例如:AService依赖BService,而BService又依赖AService。这种场景下,传统的创建顺序无法满足依赖注入的要求。

Spring的三级缓存机制

Spring通过三级缓存机制优雅地解决了循环依赖问题:

1. 第一级缓存:singletonObjects

  • 存储完全初始化完成的单例Bean

  • 这里的Bean是经过完整生命周期处理的(包括AOP代理)

2. 第二级缓存:earlySingletonObjects

  • 存储提前暴露的Bean引用(早期引用)

  • 这些Bean尚未完成完整初始化,但可以用于解决循环依赖

3. 第三级缓存:singletonFactories

  • 存储Bean的ObjectFactory(工厂对象)

  • 通过工厂可以获取Bean的早期引用,必要时创建代理对象

解决循环依赖的详细流程

场景:AService ←→ BService 相互依赖

java

// AService 依赖 BService
@Service
public class AService {@Autowiredprivate BService bService;
}// BService 依赖 AService
@Service
public class BService {@Autowiredprivate AService aService;
}

解决步骤:

  1. 开始创建AService

    • creatingSet.add('AService') - 标记AService正在创建

    • 实例化AService普通对象

  2. AService需要注入BService

    • 从单例池获取BService,不存在则开始创建BService

  3. 创建BService

    • creatingSet.add('BService') - 标记BService正在创建

    • 实例化BService普通对象

    • BService需要注入AService

  4. BService获取AService

    • 从单例池获取AService,不存在

    • 检查creatingSet发现AService正在创建(循环依赖 detected!)

    • 从三级缓存获取AService的ObjectFactory

    • 执行工厂方法:getEarlyBeanReference() → 必要时创建AService代理对象

    • 将结果存入二级缓存earlySingletonObjects,并从三级缓存移除工厂

  5. BService继续初始化

    • 注入AService的早期引用(可能是代理对象)

    • 完成其他属性填充

    • 执行AOP(如果需要)

    • 将完整BService存入单例池

  6. AService继续初始化

    • 注入已创建的BService

    • 完成其他属性填充

    • 执行AOP(但发现已提前AOP,直接使用早期引用)

    • 将完整AService存入单例池

关键源码分析

1. 添加单例工厂

java

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);}}
}

doCreateBean()方法中,Spring会提前暴露Bean的工厂:

java

// 为了解决循环依赖提前缓存单例创建工厂
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2. 获取单例Bean的逻辑

java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 从一级缓存查找Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 2. 从二级缓存查找singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// 3. 从三级缓存查找并创建早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 移动到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

3. 提前AOP处理

java

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {// 如果需要AOP,则创建代理对象Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;}return bean;
}

为什么需要三级缓存?

  1. 一级缓存:存储完整的单例Bean,保证单例性质

  2. 二级缓存:存储早期引用,避免多次执行AOP产生多个代理对象

  3. 三级缓存:存储工厂对象,延迟决策是否需要进行AOP

如果只有二级缓存,每次获取早期引用都需要执行AOP,可能导致创建多个不同的代理对象,违反单例原则。

使用注意事项


文章转载自:

http://cf9tCHYr.gjwkL.cn
http://cBNkZs1d.gjwkL.cn
http://CClO4PMj.gjwkL.cn
http://Wk3P5bdu.gjwkL.cn
http://Jfzqy2xM.gjwkL.cn
http://irVxAtZk.gjwkL.cn
http://MnD2YNYe.gjwkL.cn
http://aWBfVgIa.gjwkL.cn
http://RTrRlMKV.gjwkL.cn
http://Bk77k07p.gjwkL.cn
http://xOAg2iYN.gjwkL.cn
http://eI5rb5mb.gjwkL.cn
http://omgDHNHd.gjwkL.cn
http://fzp6fe61.gjwkL.cn
http://bVz3SE0R.gjwkL.cn
http://2FdjUSWJ.gjwkL.cn
http://6kcSnQYa.gjwkL.cn
http://vS8q6O0I.gjwkL.cn
http://utvkQfPm.gjwkL.cn
http://7hwuSAzs.gjwkL.cn
http://JKSOdHnQ.gjwkL.cn
http://evqFgect.gjwkL.cn
http://P2wNA68z.gjwkL.cn
http://jpdRDrLo.gjwkL.cn
http://wCovHhoh.gjwkL.cn
http://9AXgyR3h.gjwkL.cn
http://b6K9RSsB.gjwkL.cn
http://0WaQTi5e.gjwkL.cn
http://R8i0Usqq.gjwkL.cn
http://QSkOHS43.gjwkL.cn
http://www.dtcms.com/a/385565.html

相关文章:

  • 第四章:职业初印象:打造你的个人品牌(1)
  • (九)Python高级应用-文件与IO操作
  • FFmpeg06:SDL渲染
  • javadoc命令 错误: 编码 GBK 的不可映射字符 (0x80)
  • 【面试场景题】自增主键、UUID、雪花算法都有什么问题
  • 数据整理器(Data Collators)总结 (95)
  • 代码评价:std::shared_ptr用法分析
  • 23种设计模式案例
  • AI Agent案例与实践全解析:字节智能运维
  • MyBatis-Plus分页插件实现导致total为0问题
  • S32DS仿真环境问题
  • 黑马JavaWeb+AI笔记 Day07 Web后端实战(部门管理模块)
  • 【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维
  • kimi-k2论文阅读笔记
  • [SC]一个使用前向声明的SystemC项目例子
  • Gunicorn 部署与调优全指南(2025 版)
  • 第二十一篇|新宿平和日本语学校的结构化解读:费用函数、文化网络与AI教育建模
  • 数据结构(C语言篇):(十五)二叉树OJ题
  • RIFE.py代码学习 自学
  • Gateway-路由-规则配置
  • 低端影视官网入口 - 免费看影视资源网站|网页版|电脑版地址
  • 【Python3教程】Python3高级篇之日期与时间
  • 计算机网络——传输层(25王道最新版)
  • 5-14 forEach-数组简易循环(实例:数组的汇总)
  • 【智能体】rStar2-Agent
  • ego(5)---Astar绕障
  • UE5C++编译遇到MSB3073
  • 记一次JS逆向学习
  • 【PyTorch】单目标检测
  • RabbitMQ—基础篇