从报错到源码把Spring循环依赖彻底讲清楚如果你写Spring项目时间够长大概率见过下面这个异常BeanCurrentlyInCreationException: Error creating bean with name userService: Requested bean is currently in creation: Is there an unresolvable circular reference?我刚工作那会儿第一次看到这个报错整个人是懵的。明明两个类互相引用是很自然的写法Spring为什么非要拦着后来读了源码才明白它不是在拦你而是在某些场景下确实没法帮你。这篇我就围绕Spring 3.0的源码实现把循环依赖的来龙去脉、三级缓存的设计逻辑、以及实战里怎么排查和规避一次讲清楚。不管你是准备面试、还是被线上问题折腾得头疼这篇文章的目标就一个让你看完之后再遇到循环依赖能直接定位到源码层面知道Spring做了什么、没做什么、以及你自己该做什么。1. 循环依赖的完整画像理解问题的本质1.1 什么叫循环依赖Spring为什么会报错先明确概念。循环依赖就是两个或者多个Bean互相引用形成一个环。最常见的就是两个类互相注入Service public class UserService { Autowired private OrderService orderService; } Service public class OrderService { Autowired private UserService userService; }创建UserService时需要OrderService创建OrderService时又需要UserService死锁。如果Spring不做特殊处理这个环永远解不开。但这只是表象。真正的问题是Spring默认的单例Bean生命周期是“先实例化、再填充属性、最后初始化”。实例化只是new出对象属性填充才是把依赖的Bean塞进来。也就是说一个Bean在属性填充阶段就已经“存在”了只是还没初始化完。这个时间差就是破解循环依赖的关键窗口。Spring能解决大部分循环依赖靠的就是“让某个Bean提前暴露出去让依赖它的Bean先拿到这个半成品”。1.2 哪些循环依赖能解哪些不能解不是所有循环依赖Spring都能兜底。我直接给结论后面再解释原因单例模式 setter注入 / 字段注入能解决这是Spring最擅长的场景。单例模式 构造器注入不能解决启动直接报错。原型模式Prototype无论如何都不能解决因为原型Bean每次都是新建根本没有“缓存”一说。单例 代理类比如Async、Transactional切面容易出问题原因是代理对象创建时机和普通Bean不一样后面单独讲。很多人在网上看到“Spring用三级缓存解决循环依赖”就以为所有循环依赖Spring都能处理这其实是误解。源码里DefaultSingletonBeanRegistry的注释写得很清楚只处理单例Bean的循环引用而且只在属性填充阶段处理。1.3 从报错信息反推排查方向如果你遇到BeanCurrentlyInCreationException先从这几个方向排查确认所有涉及循环依赖的Bean是否为单例有没有加Scope(prototype)。确认是不是用了构造器注入。Spring官方其实推荐构造器注入但一旦出现循环依赖构造器注入就是死路。确认是否涉及Async、Transactional这类会触发AOP代理的注解这类Bean的循环依赖处理有额外限制。我以前排查过一个线上启动失败的问题翻来覆去查Bean配置最后发现是一个类加了Async另一个类普通字段注入它两者互相引用启动直接崩。这个场景普通三级缓存救不了得手动处理。2. 源码逐行拆解三级缓存到底做了什么2.1 DefaultSingletonBeanRegistry的三个Map循环依赖的核心实现在DefaultSingletonBeanRegistry里。这个类维护了三个缓存也就是常说的“三级缓存”/** Cache of singleton objects: bean name -- bean instance */ private final MapString, Object singletonObjects new ConcurrentHashMap(256); /** Cache of singleton factories: bean name -- ObjectFactory */ private final MapString, ObjectFactory? singletonFactories new HashMap(16); /** Cache of early singleton objects: bean name -- bean instance */ private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16);逐个说清楚singletonObjects一级缓存存的是“完整的、初始化好的Bean”。getBean拿到的最终产品都从这里出。earlySingletonObjects二级缓存存的是“提前暴露的早期Bean”。这些Bean已经实例化但属性还没填充完、初始化方法还没执行。singletonFactories三级缓存存的是ObjectFactory。这不是Bean本身而是一个“工厂”调用getObject()时才会真正产出早期Bean引用。问题来了为什么需要三级缓存二级缓存明明已经能解决循环依赖了为什么还要加一层ObjectFactory核心原因是AOP代理。如果一个Bean最终需要返回代理对象那么早期暴露出来的“引用”必须是代理对象不然依赖它的Bean拿到的原始对象就和最终对象不一致。三级缓存里放ObjectFactory就是为了在需要暴露早期引用时临时执行getEarlyBeanReference()方法如果Bean有代理需求就提前生成代理。有人说“没有AOP二级缓存就够了”这句话在单机场景下是对的。但Spring要考虑的是通用性不能假设用户不用AOP。所以三级缓存是设计上最稳妥的方案。2.2 getSingleton的完整流程看完了缓存结构再看核心方法getSingleton。这个方法在Bean创建过程中被反复调用是理解循环依赖的主线。protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null) { ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }这段代码的逻辑可以拆成四步先查一级缓存命中就直接返回这是最理想的情况说明Bean已经完全创建好了。一级缓存没命中再看二级缓存。二级缓存有值说明这是提前暴露的早期Bean直接返回。二级缓存也没有且允许提前引用allowEarlyReferencetrue就从三级缓存里拿ObjectFactory执行getObject()生成早期引用。生成的早期引用放入二级缓存同时从三级缓存移除。这样下次再查就直接命中二级缓存不用重复走工厂。注意一个细节代码里对singletonObjects做了synchronized同步但earlySingletonObjects和singletonFactories的读写也在同步块里。这是为了保证并发场景下缓存状态的一致性。Spring框架在并发处理上非常谨慎Bean创建本身就是高频操作这个锁的存在是有必要的。还有一个细节值得注意isSingletonCurrentlyInCreation(beanName)。这个方法的返回值是核心判断条件——只有“正在创建中”的Bean才会走提前暴露的流程。singletonsCurrentlyInCreation这个Set就是用来记录正在创建中的Bean名字的。2.3 什么时候把Bean放入三级缓存三级缓存里的ObjectFactory是什么时候放进去的答案在AbstractAutowireCapableBeanFactory的doCreateBean方法里。protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { // 1. 实例化 BeanWrapper instanceWrapper null; if (mbd.isSingleton()) { instanceWrapper this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper null) { instanceWrapper createBeanInstance(beanName, mbd, args); } final Object bean instanceWrapper.getWrappedInstance(); // 2. 提前暴露放入三级缓存 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, new ObjectFactoryObject() { Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } // 3. 属性填充 Object exposedObject bean; try { populateBean(beanName, mbd, instanceWrapper); if (exposedObject ! null) { exposedObject initializeBean(beanName, exposedObject, mbd); } } catch (Throwable ex) { // ... } // ... }关键就在第二步。条件有三个mbd.isSingleton()必须是单例。this.allowCircularReferences全局开关默认true。你可以通过SpringBoot的spring.main.allow-circular-referencesfalse关掉它。isSingletonCurrentlyInCreation(beanName)当前Bean正在创建过程中。三个条件同时满足就会执行addSingletonFactory把ObjectFactory放进三级缓存。注意这个ObjectFactory返回的是getEarlyBeanReference(beanName, mbd, bean)的结果而bean是刚实例化出来的那个原始对象。addSingletonFactory的方法实现很简单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); } } }放进singletonFactories时同时清掉earlySingletonObjects里可能存在的旧值。逻辑上两者不会同时存在但Spring还是会做防御性清理。2.4 getEarlyBeanReference三级缓存为什么能处理AOPgetEarlyBeanReference是三级缓存里最有技术含量的方法。它决定了一个需要代理的Bean在提前暴露时返回的是“原始对象”还是“代理对象”。protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }SmartInstantiationAwareBeanPostProcessor是个关键扩展点。AbstractAutoProxyCreator就实现了这个方法它的逻辑是如果当前Bean有匹配的Advisor切面就直接创建代理对象并返回如果没有代理需求返回原始对象。Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { this.earlyProxyReferences.add(cacheKey); } return wrapIfNecessary(bean, beanName, cacheKey); }这里有个精妙的设计如果Bean需要代理那么早期暴露时创建一次代理后面正式初始化阶段就不会再创建第二次代理。因为wrapIfNecessary会检查earlyProxyReferences缓存如果这个Bean已经处理过就不再重复代理。这意味着循环依赖Bean的代理对象是在三级缓存阶段就确定的。2.5 getObjectForBeanInstance最终产物怎么交付Bean创建完成后最终调用的是getSingleton(String beanName, ObjectFactory? singletonFactory)方法。这个方法里做的第一件事就是把创建好的单例Bean放进singletonObjects一级缓存。public Object getSingleton(String beanName, ObjectFactory? singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { // ... beforeSingletonCreation(beanName); boolean newSingleton false; try { singletonObject singletonFactory.getObject(); newSingleton true; } catch (BeanCreationException ex) { // ... } finally { afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }注意beforeSingletonCreation和afterSingletonCreation这两个方法。beforeSingletonCreation会把beanName加入singletonsCurrentlyInCreationafterSingletonCreation会移除它。这就是之前说的判断依据。最后一步是addSingleton把Bean放入一级缓存同时清理二三级缓存protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }到这里一个Bean的生命周期就走完了完整闭环实例化 - 放入三级缓存 - 属性填充 - 初始化 - 放入一级缓存 - 清理二三级缓存。3. 实战推演两个Bean互相依赖的完整过程3.1 打电话的场景模拟为了好理解我用一个打电话的类比。假设有两个人A和B他们约好互相打电话。A先拨给BB还没接A就先把电话“挂机但没完全挂”半成品已实例化但没初始化完并向通讯录登记“A的号码可用”。这时候B的回拨打进来通讯录查到A的号码B就顺利拨通了A。等B通完话挂了机完整初始化A再从通讯录拿到B的完整号码完成自己的通话。整个过程中通讯录就是三级缓存。3.2 getUserService、getOrderService的代码级推演这个推演过程非常重要我建议你跟着走一遍因为面试时考循环依赖本质上考的就是这个过程。假设容器启动先创建UserServicegetSingleton(UserService)检查一级缓存没有。创建UserService实例。此时实例化完成放入三级缓存等待属性填充。populateBean填充UserService属性发现需要OrderService。getSingleton(OrderService)检查一级缓存没有检查二级缓存没有检查三级缓存没有。创建OrderService实例放入三级缓存。populateBean填充OrderService属性发现需要UserService。getSingleton(UserService)检查一级缓存没有检查二级缓存没有检查三级缓存有ObjectFactory。调用ObjectFactory.getObject()执行getEarlyBeanReference生成UserService的早期引用。早期引用放入二级缓存移除三级缓存中的UserService工厂。OrderService拿到UserService的早期引用完成属性填充。OrderService执行初始化方法完成后放入一级缓存。回到UserService的populateBean从一级缓存拿到OrderService完成属性填充。UserService执行初始化方法完成后放入一级缓存。整个流程的核心就一句话Spring在填充UserService之前先把UserService的半成品暴露给了OrderService。等OrderService创建完毕UserService的一切需求都能从缓存中满足。3.3 为什么构造器注入不行构造器注入的循环依赖解决不了原因在于时机。构造器注入的情况下在new UserService的时候就需要OrderService实例。此时UserService还没完成实例化不可能放入三级缓存。Spring尝试创建OrderService而OrderService构造器又需要UserService又回过来找UserService此时UserService的singletonsCurrentlyInCreation已经有记录但三级缓存里没有它的工厂因为根本没走到addSingletonFactory那一步。所以直接抛BeanCurrentlyInCreationException。这个限制也体现了源码里那句注释的意义循环依赖的解决方案依赖“实例化和属性填充之间”的窗口期构造器注入把这个窗口堵死了。3.4 为什么原型Bean不行原型Bean每次getBean都是全新创建不做缓存也没有注册到singletonsCurrentlyInCreation因为beforeSingletonCreation只对单例生效。原型Bean之间互相依赖就是无解的递归调用栈溢出或者直接抛异常。Spring官方也明确说过不支持原型Bean的循环依赖想都别想。4. Spring解决不了的场景Async与循环依赖的经典坑4.1 现象和报错现场先看一个我在实际项目中遇到的例子Service public class AService { Autowired private BService bService; Async public void doSomething() { // ... } } Service public class BService { Autowired private AService aService; }启动时直接报BeanCurrentlyInCreationException: Error creating bean with name aService: Requested bean is currently in creation: Is there an unresolvable circular reference?你可能会说AService和BService就普通字段注入啊为什么处理不了问题出在Async上。Async注解会触发AsyncAnnotationBeanPostProcessor给Bean生成代理对象。这个代理是在initializeBean阶段生成的不是在getEarlyBeanReference阶段。而循环依赖触发时BService需要的是AService的早期引用此时getEarlyBeanReference返回的是原始对象不是代理对象。结果就是BService拿到了AService的原始对象但AService的最终Bean是代理对象。两者不一致Spring在initializeBean最后做检查时发现earlySingletonReference和exposedObject不是同一个对象直接报错。源码里这段检查逻辑在doCreateBean的最后if (earlySingletonExposure) { Object earlySingletonReference getSingleton(beanName, false); if (earlySingletonReference ! null) { if (exposedObject bean) { exposedObject earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping hasDependentBean(beanName)) { String[] dependentBeans getDependentBeans(beanName); SetString actualDependentBeans new LinkedHashSet(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, Bean with name beanName has been injected into other beans [ StringUtils.collectionToCommaDelimitedString(actualDependentBeans) ] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using getBeanNamesOfType with the allowEagerInit flag turned off.); } } } }大意就是早期暴露出去的原始对象和最终被包装后的代理对象不一致而且确实有其他Bean依赖了早期版本那就直接抛异常宁可启动失败也不埋雷。4.2 三个解决方案这个场景我总结了三种解法按推荐程度从高到低方案一把Async去掉或者不要形成AService和BService的直接循环。最好的办法是重构依赖关系比如把Async方法抽到独立的异步任务类里。方案二给循环依赖的一方加Lazy。Service public class BService { Autowired Lazy private AService aService; }Lazy会生成一个代理对象注入BServiceBService拿到的不是AService的真实引用而是一个代理。调用AService方法时才真正解析。这样BService不依赖AService的早期引用避开了循环依赖的处理逻辑。方案三手动将AService设为非单例。不推荐会引入更多问题。我一般用方案二改动最小而且语义明确。但方案二本质上也是一种“绕过”彻底解法还是打破循环依赖。4.3 如何通过allowRawInjectionDespiteWrapping控制源码里有个参数allowRawInjectionDespiteWrapping默认false。如果设为true即使早期暴露的是原始对象、最终Bean是代理对象Spring也不报错直接让早期暴露的那个原始对象继续被使用。这个参数不建议在生产环境打开。它属于“知道有这么回事”就行实际使用会破坏代理语义导致AOP功能失效。5. 从设计层规避循环依赖的本质是设计问题5.1 循环依赖是不是一定要解决我的观点是循环依赖本身是设计上的坏味道。两个类互相依赖往往说明它们在职责边界上划分得不够清晰。但现实项目里代码是很多人一起写的依赖关系不会完美。所以我的建议分两层线上已经出了问题先解决启动问题用Lazy兜底保证服务能起来。从长远看必须重构打破循环依赖。最常用的手段是把互相依赖的逻辑抽到第三个类里或者用事件机制解耦。5.2 ApplicationContext里还有一个缓存earlyProxyReferences除了DefaultSingletonBeanRegistry的三个MapAbstractAutoProxyCreator里也有一个缓存需要注意earlyProxyReferences。它是一个Mapkey是beanNamevalue是bean的原始对象。这个缓存的作用是标记“这个Bean的早期代理已经处理过了”。Spring用它在后续的初始化阶段避免重复创建代理。如果你在分析源码时把earlyProxyReferences和三级缓存混在一起看很容易绕晕。其实它是AOP层面自己的缓存不属于循环依赖的三级缓存体系。5.3 Spring Boot 2.6以后的变化Spring Boot 2.6版本开始官方默认把spring.main.allow-circular-references设为了false。这意味着你写代码时如果出现循环依赖Spring Boot会直接拒绝启动而不是像以前一样“能忍则忍”。这是一个非常信号Spring官方希望开发者不要在代码里留循环依赖。如果你还在用老版本升级到Spring Boot 2.6以上遇到循环依赖导致的启动失败不要慌先看异常信息再决定是临时开启allow-circular-references还是趁机重构依赖关系。实测下来在旧项目里遇到这种升级问题最稳妥的做法是全局开启allow-circular-references先保证业务不中断然后列一个循环依赖清单逐步优化。不建议一上来就重构线上稳定优先。5.4 结合Spring AI、Spring Cloud等场景的思考现在很多新项目用Spring AI、Spring Cloud Alibaba组件模块之间通过模块划分和接口调用很少会写出手工循环依赖。但有个场景容易踩坑自定义Starter或内部封装库时Bean的装配顺序不受你控制容易出现隐式循环依赖。比如我在一个Spring AI项目中自研了一个服务编排组件编排器和执行器互相引用执行器里又通过注入ApplicationContext去拿编排器启动就报了循环依赖。当时排查起来特别费劲因为Bean不在同一个业务模块里。后来解决办法还是从设计层面把编排器和执行器的依赖关系切了改用事件订阅。这类问题在业务代码里可能不明显但在框架封装、底层组件里很容易冒出来处理原则是一样的能拆就拆不要靠着三级缓存续命。6. 手写迷你循环依赖20行代码复现核心原理想真正理解三级缓存最好的方式不是看源码而是自己写一个简化版。我用一段极简代码把核心逻辑复现出来你对着它再看源码思路会清晰很多public class CircularDependencyDemo { // 一级缓存完整Bean private final MapString, Object singletonObjects new HashMap(); // 二级缓存早期Bean private final MapString, Object earlySingletonObjects new HashMap(); // 三级缓存Bean工厂 private final MapString, SupplierObject singletonFactories new HashMap(); // 正在创建中的Bean private final SetString singletonsCurrentlyInCreation new HashSet(); public Object getBean(String beanName) { Object bean singletonObjects.get(beanName); if (bean ! null) { return bean; } return doCreateBean(beanName); } private Object getEarlyBean(String beanName) { if (earlySingletonObjects.containsKey(beanName)) { return earlySingletonObjects.get(beanName); } SupplierObject factory singletonFactories.get(beanName); if (factory ! null) { Object earlyBean factory.get(); earlySingletonObjects.put(beanName, earlyBean); singletonFactories.remove(beanName); return earlyBean; } return null; } private Object doCreateBean(String beanName) { singletonsCurrentlyInCreation.add(beanName); Object bean createInstance(beanName); // 把工厂放入三级缓存这里直接返回原始对象 singletonFactories.put(beanName, () - bean); Object finalBean bean; // 模拟属性填充把依赖注入进去 if (userService.equals(beanName)) { Object orderService getBean(orderService); try { finalBean.getClass().getField(orderService).set(finalBean, orderService); } catch (Exception e) { throw new RuntimeException(e); } } if (orderService.equals(beanName)) { Object userService getEarlyBean(userService); try { finalBean.getClass().getField(userService).set(finalBean, userService); } catch (Exception e) { throw new RuntimeException(e); } } singletonsCurrentlyInCreation.remove(beanName); singletonObjects.put(beanName, finalBean); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return finalBean; } private Object createInstance(String beanName) { try { if (userService.equals(beanName)) { return new UserService(); } if (orderService.equals(beanName)) { return new OrderService(); } } catch (Exception e) { throw new RuntimeException(e); } return null; } public static class UserService { public OrderService orderService; } public static class OrderService { public UserService userService; } public static void main(String[] args) throws Exception { CircularDependencyDemo container new CircularDependencyDemo(); UserService userService (UserService) container.getBean(userService); System.out.println(userService.orderService.userService userService); } }这个简化版没有处理AOP代理、没有处理并发、没处理BeanPostProcessor但核心思路完全一致getUserService时发现需要orderService。getOrderService时发现需要userService。userService在三级缓存里getEarlyBean直接取出来。orderService填充完毕userService继续填充两者都进入一级缓存。跑一下main方法输出true说明循环引用被成功解析。写代码的时候注意我这里直接用反射给字段赋值是为了省掉setter。真实场景里Spring用的是PropertyDescriptor或者构造器机制类似但复杂度高很多。7. 高频面试题关于循环依赖你该怎么答7.1 Spring是如何解决循环依赖的推荐答法分四点底层数据结构DefaultSingletonBeanRegistry里维护三级缓存一级存完整Bean、二级存早期Bean、三级存ObjectFactory工厂。核心流程Bean实例化后立刻放入三级缓存属性填充时如果需要其他Bean就递归创建那个Bean创建过程中如果发现目标Bean正在创建中就从三级缓存里获取早期引用。三级缓存的价值解决AOP代理场景下早期引用和最终代理对象不一致的问题。getEarlyBeanReference中会触发SmartInstantiationAwareBeanPostProcessor创建代理。边界条件只能处理单例Bean的字段注入和setter注入构造器注入和原型Bean无解。7.2 为什么不用两级缓存这是高频追问。答的时候抓住“AOP代理时机”这个点。如果只有两级缓存那三级缓存放的早期Bean只能是原始对象。问题是一个需要AOP代理的Bean它的最终形态是代理对象而早期暴露出去的却是原始对象两者不一致。在非循环依赖场景下没问题代理在Bean初始化后生成但循环依赖场景下依赖方在初始化完成前就拿到了引用只能是原始对象。三级缓存解决了这个问题ObjectFactory的getObject()会执行getEarlyBeanReference在这里提前创建代理对象。所以它是为AOP场景设计的机制。7.3 构造器注入和Async循环依赖为什么不行构造器注入循环依赖的处理依赖“实例化和属性填充之间的窗口期”构造器注入把这个窗口堵死了Bean还没实例化完成就要注入依赖自然拿不到早期引用。Async循环依赖早期暴露的是原始对象但最终Bean被包装成了代理对象Spring检测到这种不一致后宁可抛异常。7.4 表格总结不同场景的循环依赖支持情况场景是否支持失败原因单例 setter/字段注入支持三级缓存提供早期引用单例 构造器注入不支持实例化阶段无法提前暴露原型 任意注入不支持每次新建不缓存无早期暴露DI注入 Lazy支持代理延迟解析绕过循环循环依赖 Async不支持/易报错原始对象与代理对象不一致8. 写在最后的实践盘点和常见问题速查这个项目折腾下来核心就这三句话第一三级缓存不是万能药它解决的是“Bean在属性填充阶段互相依赖”的问题。第二循环依赖的最终解决要靠设计不是靠框架。遇到循环依赖先想代码结构是不是有问题再想Spring能不能帮你。第三源码不要只看一遍要配合断点调试。我自己学习三级缓存时是写了一个最小的Spring项目然后全程加断点跑了一遍看着singletonObjects、earlySingletonObjects、singletonFactories三个Map里的数据流转才真正理解它。强烈建议你也这么做。最后把常见问题的速查表放在这方便以后排查报错/现象大概率原因推荐解法BeanCurrentlyInCreationException构造器注入导致循环依赖改用setter注入或加LazyBeanCurrentlyInCreationException Async代理对象和原始对象不一致Lazy打破循环或拆类PrototypeBean循环依赖原型模式不支持循环依赖改为单例或手动控制创建Spring Boot 2.6以上启动失败allow-circular-references默认关闭开配置或重构依赖内存中出现重复代理对象earlyProxyReferences误用检查自定义BeanPostProcessor其实往深了看Spring的三级缓存是一种“时空换设计”的典型思路。它牺牲了一点内存缓存三个Map换来了Bean生命周期的灵活性和AOP的无缝集成。这种设计取舍比循环依赖本身更值得思考。希望在你自己动手分析源码时能保持这种“为什么这样设计”的追问习惯。
