真正出问题的是提前暴露的早期引用与AOP代理逻辑不匹配,而非Lambda本身持有代理;Lambda仅是三级缓存中执行getEarlyBeanReference的载体,其执行时机和返回结果被干扰才会破坏循环依赖解耦机制。

这个问题其实不是“Lambda内部持有代理对象”导致循环依赖,而是对 Spring 三级缓存和代理时机的理解偏差造成的误判。真正出问题的环节,往往发生在 提前暴露的早期引用 与 AOP 代理逻辑不匹配 时——而 Lambda 表达式(即 ObjectFactory)只是承载这个逻辑的载体,它本身不会“错误持有”,但它的执行时机和返回结果若被干扰,就会破坏整个循环依赖解耦机制。
确认是否真由 Lambda + 代理引发的问题
Spring 的三级缓存中,三级缓存(singletonFactories)存的就是形如 () -> getEarlyBeanReference(...) 的 Lambda。它只在发生循环依赖时才被触发执行,目的是决定:此时该返回原始 Bean,还是已增强的代理对象。
排查第一步不是看 Lambda 写得对不对,而是验证:
- 你的 Bean 是否启用了 AOP(比如加了
@Transactional、@Async或自定义切面) - 该 Bean 是否在属性注入阶段(
populateBean)就被其他 Bean 引用(即触发了早期引用) - 最终放入二级缓存(
earlySingletonObjects)的是原始对象,还是代理对象?可通过调试断点在addSingletonFactory()和getEarlyBeanReference()处观察返回值
常见干扰 Lambda 正常执行的典型场景
以下情况会让 Lambda 返回不符合预期的对象,从而让依赖方拿到“半成品却带代理”的混乱引用,后续初始化时再覆盖又不一致,最终报 BeanCurrentlyInCreationException 或空指针:
-
手动调用
ApplicationContext.getBean()强制触发早期获取:比如在@PostConstruct或初始化方法里主动取另一个正在创建中的 Bean,绕过了 Spring 自动管理的缓存查找路径,导致代理逻辑未走getEarlyBeanReference,而是直接返回了原始对象 -
自定义
SmartInstantiationAwareBeanPostProcessor干预了getEarlyBeanReference:比如在该方法中无条件返回了新代理,或返回了未完成初始化的代理,破坏了“同一 Bean 在早期和最终阶段引用一致性”原则 -
使用了
@Lazy但配合了非标准初始化顺序:例如@Lazy的 Bean 又被一个非懒加载的 Bean 通过 setter 注入,而后者在自身初始化前就调用了前者的方法——此时 Lambda 被提前执行,但代理尚未准备好
定位问题的实操步骤
不用猜,靠日志和断点直击关键节点:
- 启动时加上
-Dspring.trace=true或配置logging.level.org.springframework.beans=DEBUG,关注日志中类似Creating shared instance of singleton bean 'a'和Returning eagerly cached instance of singleton bean 'a'的输出顺序 - 在
DefaultSingletonBeanRegistry.getSingleton(String)方法入口设断点,观察每次查找时三个缓存(singletonObjects、earlySingletonObjects、singletonFactories)的命中情况 - 在
AbstractAutowireCapableBeanFactory.getEarlyBeanReference()中检查传入的bean是原始实例还是已有代理;再对比最终getSingleton()返回的成品 Bean,二者==是否为 true - 若发现早期引用是代理、成品却是原始对象(或反之),说明 AOP 后置处理器在不同阶段行为不一致,重点审查
SmartInstantiationAwareBeanPostProcessor实现
修复建议:保持早期与最终引用语义一致
核心原则是:Spring 允许你提前暴露一个对象,但这个对象必须能代表它“最终的样子”。如果最终是代理,早期也应是代理;如果最终是原始对象,早期就不该有代理。
- 避免在
getEarlyBeanReference中新建代理(如 new CglibAopProxy(...)),应复用容器已准备好的逻辑,优先调用父类实现 - 若必须定制代理,确保该代理对象在后续
initializeBean阶段不会被替换或重新包装 - 对高风险 Bean,可显式禁用早期暴露:
mbd.setAllowCaching(false)(需自定义BeanDefinition),强制走完整流程,以失败换明确性 - 更彻底的解法:重构依赖关系,用事件(
ApplicationEvent)、回调接口或引入中间协调者,从设计上消除双向强依赖










