问题根源是spring三级缓存与aop代理时机不一致,导致early reference与最终bean类型不匹配;需检查aop启用、早期引用触发点及getearlybeanreference返回值一致性。

这个问题其实不发生在“虚拟机栈帧内部”,而是对 Spring 三级缓存与代理时机的理解偏差导致的误判。真正出问题的环节,是提前暴露的早期引用(early reference)和 AOP 代理逻辑不一致——栈帧只是执行上下文,它本身不会“持有”代理对象;所谓“错误持有”,本质是 getEarlyBeanReference 返回了不符合预期的对象,破坏了同一 Bean 在早期与最终阶段引用的一致性。
先确认是否真由代理 + 循环依赖引发
Spring 的循环依赖解耦机制只对单例、setter 注入、非构造器依赖生效。排查起点不是看栈帧,而是验证以下三点:
- 目标 Bean 是否启用了 AOP:比如标注了 @Transactional、@Async 或自定义 @Aspect 切面
- 该 Bean 是否在属性注入阶段(populateBean)就被其他 Bean 引用:即触发了早期引用,而非等到初始化完成
- 最终进入二级缓存(earlySingletonObjects)的是原始对象,还是已增强的代理对象:可在 addSingletonFactory() 和 getEarlyBeanReference() 处下断点,观察返回值是否与后续 finishBeanFactoryInitialization 中的实际 Bean 一致
常见干扰 getEarlyBeanReference 正常执行的场景
这些情况会让 Lambda 形式的 ObjectFactory 返回“半成品代理”,导致依赖方拿到一个被增强但未初始化的对象,后续再覆盖时引用不一致,最终抛 BeanCurrentlyInCreationException 或 NPE:
- 在 @PostConstruct 或初始化方法中主动调用 ApplicationContext.getBean():绕过 Spring 缓存查找路径,跳过了 getEarlyBeanReference 流程,直接返回原始对象
- 自定义 SmartInstantiationAwareBeanPostProcessor 干预了 getEarlyBeanReference:例如无条件返回新代理、或返回了尚未完成初始化的代理实例
- @Lazy Bean 被非懒加载 Bean 通过 setter 注入,且后者在自身初始化前就调用了前者的方法:Lambda 被提前触发,但代理生成逻辑(如 AbstractAutoProxyCreator)尚未执行到位
定位问题的实操步骤
不用猜测,靠日志和断点直击关键节点:
- 启动时添加 JVM 参数:-Dspring.trace=true,或配置 logging.level.org.springframework.beans=DEBUG
- 在 DefaultSingletonBeanRegistry.getSingleton(String, boolean) 中断点,观察从 singletonFactories 取出 ObjectFactory 后的执行结果
- 在 AbstractAutowireCapableBeanFactory.getEarlyBeanReference() 断点,检查每个 SmartInstantiationAwareBeanPostProcessor 的返回值是否稳定、是否与最终 Bean 类型一致
- 对比 earlySingletonObjects 和 singletonObjects 中同名 Bean 的实际 class 类型:若前者是 $ProxyXX 或 CGLIB 而后者是原始类,说明代理逻辑未对齐
修复方向比“清理栈帧”更有效
虚拟机栈帧无法也不应被“清理”——它是执行载体,不是问题根源。真正要做的,是让代理逻辑在早期和晚期保持语义一致:
- 避免在初始化生命周期内手动触发 getBean(),改用 @Lazy ObjectProvider
延迟获取 - 自定义后置处理器中,getEarlyBeanReference 应仅在必要时返回代理,且必须保证该代理可安全用于后续填充和初始化
- 对强依赖代理行为的 Bean,考虑用构造器注入替代 setter 注入,或拆分关注点(如将事务逻辑抽到 Service 层外部)











