为何SpyPostProcessor未效仿AbstractAutoProxyCreator实现getEarlyBeanReference引发循环引用
为什么
MockitoPostProcessor#SpyPostProcessor无法复用代理解决循环引用问题 核心原因
AbstractAutoProxyCreator的缓存逻辑
AbstractAutoProxyCreator是Spring AOP体系专门用于代理生成的处理器,内部维护了earlyProxyReferences缓存,缓存键为bean名称。当在getEarlyBeanReference()阶段生成代理后,会将代理对象存入该缓存;到postProcessAfterInitialization()阶段时,会优先查询缓存,如果已经存在对应bean的代理,直接返回缓存实例,不会重复生成,因此两个阶段返回的是同一个代理对象,不会出现循环引用下的包装冲突。
MockitoPostProcessor#SpyPostProcessor的设计差异
- 无提前代理缓存机制:SpyPostProcessor没有设计对应提前暴露代理的缓存逻辑,无论
getEarlyBeanReference()阶段是否生成过spy代理,在postProcessAfterInitialization()阶段都会重新生成一个全新的spy代理实例,不会复用之前的对象,直接导致两个阶段返回的代理不一致。 - Spy代理生成逻辑特殊:
@SpyBean的设计要求spy代理必须基于完全完成属性注入、初始化的成品bean实例生成,才能保证spy的拦截逻辑符合预期(比如正确保留原bean的业务逻辑、正常统计调用次数等)。如果在getEarlyBeanReference()阶段用还未完成初始化的半成品bean生成spy,后续bean初始化完成后,该spy代理的状态是错误的,因此Mockito的设计逻辑里不会提前缓存半成品的spy代理,而是等bean完全初始化后再生成最终的代理对象。 - 逻辑优先级不兼容:SpyPostProcessor的执行优先级低于Spring自带的AOP代理处理器,且其处理逻辑是直接替换原有bean实例,不会合并Spring AOP的代理缓存逻辑,也不会读取
AbstractAutoProxyCreator维护的缓存,因此无法复用AOP体系的代理缓存能力。
异常触发流程
你提供的代码属于典型的循环依赖场景:
@SpringBootTest public class MockitoTest { @SpyBean private DevComponent devComponent; @Autowired private TestComponent testComponent; @Test public void testMockito() { // ... } } @Component public class DevComponent { @Autowired private TestComponent testComponent; } @Component public class TestComponent { @Autowired private DevComponent devComponent; }
触发异常的完整流程为:
- 初始化
devComponent时,因为存在循环依赖,会提前调用getEarlyBeanReference()生成第一个spy代理,注入到testComponent中 - 等
devComponent完成全量初始化后,postProcessAfterInitialization()生成第二个全新的spy代理 - Spring检测到注入到
testComponent的代理和最终生成的代理不是同一个实例,抛出如下异常:
创建名称为'devComponent'的Bean时出错:名称为'devComponent'的Bean作为循环引用的一部分,其原始版本已被注入到其他Bean [testComponent] 中,但最终被包装。这意味着上述其他Bean没有使用该Bean的最终版本。这通常是过急的类型匹配导致的——例如,考虑使用关闭'allowEagerInit'标志的'getBeanNamesForType'方法。
常见解决方案
- 打破循环依赖:调整业务代码拆分依赖,或在注入点添加
@Lazy注解实现延迟加载 - 给
@SpyBean对应的注入点也添加@Lazy注解,避免提前触发bean的提前暴露逻辑 - 若使用Spring Boot 2.6+版本,可临时开启循环依赖支持(仅做临时验证用,不推荐线上使用)
内容的提问来源于stack exchange,提问作者SaberSword
相关产品推荐
相关产品推荐

