spring通过singletonscurrentlyincreation集合标记“正在创建中”的bean来判断循环依赖;三级缓存中以objectfactory形式提前暴露半成品对象,结合getearlybeanreference实现aop代理的按需生成与一致性保障。

Spring 判断循环依赖靠的是“正在创建中”的状态标记,提前暴露代理对象则依赖三级缓存配合 AOP 的早期引用机制——核心不是直接存 Bean,而是存一个能按需生成(并可能增强)对象的工厂。
怎么判断发生了循环依赖
Spring 在创建单例 Bean 时,会把 Bean 名称加入一个叫 singletonsCurrentlyInCreation 的 Set 集合。这个集合就是“正在创建中的 Bean 清单”。
当 A 开始创建,在实例化后、属性注入前,Spring 就会把它加进这个集合;接着 A 要注入 B,于是触发 getBean(B),B 开始创建,同样被加入该集合;B 在属性注入阶段又要注入 A —— 此时 Spring 再次调用 getBean(A),发现 A 已在 singletonsCurrentlyInCreation 中,就确认出现了循环依赖。
注意:这个检测只对单例有效,原型(Prototype)和构造器注入场景下,Spring 不做缓存回查,直接抛 BeanCurrentlyInCreationException。
为什么需要提前暴露,而不是等初始化完再注入
因为属性注入必须发生在初始化之前。如果等 A 完成初始化(含 AOP 代理生成),B 才能拿到 A,那 B 的属性注入就无法完成,整个流程卡死。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
所以 Spring 必须在 A 实例化完成、但尚未初始化时,就让它“可被其他 Bean 引用”——这就是“提前暴露”。暴露的不是原始对象本身,而是一个 ObjectFactory,形如:
() -> getEarlyBeanReference(bean, beanName)
这个 Lambda 是三级缓存(singletonFactories)里存的内容,它延迟执行,确保每次取的时候都能决定:返回原始对象,还是已代理的对象。
代理对象怎么提前生成并保持一致
AOP 代理默认在初始化后置处理器(postProcessAfterInitialization)中生成。但循环依赖要求代理逻辑前移,关键就在 getEarlyBeanReference 方法:
- 如果 Bean 启用了 AOP(比如有 @Transactional 或自定义切面),AbstractAutoProxyCreator 会在 getEarlyBeanReference 中尝试生成代理
- 该方法被三级缓存里的 ObjectFactory 调用,时机是:另一个 Bean(如 B)在属性注入时首次请求 A
- 生成的代理对象会被放入二级缓存 earlySingletonObjects,后续所有对该 Bean 的早期引用都拿到同一个代理实例
- 等 A 初始化完成后,最终代理对象会覆盖进一级缓存 singletonObjects,保证“早期引用”和“最终引用”语义一致
常见踩坑点:代理提前暴露失败的原因
不是所有情况都能顺利提前生成代理,以下操作容易破坏一致性:
- 在 @PostConstruct 或初始化方法里手动调用 ApplicationContext.getBean(),绕过 Spring 缓存链路,导致跳过 getEarlyBeanReference,拿到原始对象
- 自定义 SmartInstantiationAwareBeanPostProcessor 干预了 getEarlyBeanReference,返回了未完成初始化的代理或空代理
- 混用 @Lazy 和非懒加载 Bean,导致 ObjectFactory 提前执行,但代理基础设施(如 Advisor)还没准备好
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










