一、什么是循环依赖?
循环依赖(Circular Dependency)就像两个程序员互相等待对方提交代码的场景:A说"我的代码要调用B的类",B说"但我的类需要A的接口定义"。在Spring中具体表现为:
java">@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}
这两个服务类就像"先有鸡还是先有蛋"的问题,传统的对象创建方式根本无法解决这种相互依赖关系。但Spring通过巧妙的三级缓存设计,让这个看似无解的问题迎刃而解。
二、Spring Bean生命周期关键节点
要理解解决方案,先要掌握Bean创建的核心步骤:
-
实例化(Instantiate):
new ServiceA()
创建原始对象 -
属性填充(Populate):通过反射注入依赖
-
初始化(Initialize):执行@PostConstruct等方法
三、三级缓存机制全景解析
Spring在DefaultSingletonBeanRegistry
中维护了三个关键缓存:
缓存名称 | 存储内容 | 级别 |
---|---|---|
singletonObjects | 完整的单例Bean | 一级 |
earlySingletonObjects | 提前暴露的早期引用 | 二级 |
singletonFactories | 创建Bean的ObjectFactory工厂对象 | 三级 |
处理流程详解(以ServiceA和ServiceB为例)
-
创建ServiceA
-
实例化ServiceA原始对象
-
将ObjectFactory存入三级缓存(singletonFactories)
-
开始属性注入,发现需要ServiceB
-
-
创建ServiceB
-
实例化ServiceB原始对象
-
将ObjectFactory存入三级缓存
-
属性注入时发现需要ServiceA
-
-
获取ServiceA早期引用
-
完成ServiceB创建
-
继续完成ServiceB的属性注入和初始化
-
将ServiceB存入一级缓存
-
-
回填ServiceA
-
用已创建的ServiceB完成属性注入
-
执行初始化方法
-
将ServiceA存入一级缓存
-
四、源码级深度剖析
关键源码在AbstractAutowireCapableBeanFactory
:
java">protected Object doCreateBean(...) {// 1. 实例化BeaninstanceWrapper = createBeanInstance(beanName, mbd, args);// 2. 加入三级缓存(重要!)addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 3. 属性注入populateBean(beanName, mbd, instanceWrapper);// 4. 初始化initializeBean(beanName, exposedObject, mbd);
}
缓存操作核心方法:
java">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);}}
}
五、不同注入方式的差异
注入方式 | 是否支持解决循环依赖 | 原因分析 |
---|---|---|
Setter注入 | ✅ | 属性注入在对象实例化之后 |
字段注入 | ✅ | 同Setter注入 |
构造器注入 | ❌ | 实例化前就需要完成注入 |
构造器注入失败示例:
java">@Service
public class ServiceC {private final ServiceD serviceD;@Autowiredpublic ServiceC(ServiceD serviceD) {this.serviceD = serviceD;}
}@Service
public class ServiceD {private final ServiceC serviceC;@Autowiredpublic ServiceD(ServiceC serviceC) {this.serviceC = serviceC;}
}
这种情况会直接抛出BeanCurrentlyInCreationException
六、应用场景与限制条件
适用场景
-
单例作用域(Singleton)的Bean
-
非构造器注入方式
-
没有同时使用AOP代理的情况
限制条件
-
原型(Prototype)作用域的Bean无法解决
-
需要开启allowCircularReferences(默认true)
-
存在AOP代理时需要特殊处理(通过ObjectFactory延迟生成代理)
七、最佳实践建议
-
架构设计层面
-
尽量避免循环依赖,使用中介者模式解耦
-
将公共逻辑抽取到第三方的Common模块
-
-
编码实现层面
-
优先使用构造器注入明确依赖关系
-
对于必须的循环依赖使用@Lazy延迟加载
-
复杂场景考虑使用ApplicationContext.getBean()
-
-
调试技巧
八、常见问题解答
Q:为什么需要三级缓存而不是两级?
A:主要是为了处理AOP代理的情况。ObjectFactory可以延迟决定返回原始对象还是代理对象,保证最终注入的Bean类型正确。
Q:Spring如何检测循环依赖?
A:通过DefaultSingletonBeanRegistry
的singletonsCurrentlyInCreation
集合记录正在创建的Bean,当发现重复创建时抛出异常。
Q:多级循环依赖(如A->B->C->A)能解决吗?
A:可以,只要依赖链是setter/字段注入且都是单例Bean,三级缓存机制可以处理任意长度的循环链。
九、总结
Spring的三级缓存设计充分体现了"空间换时间"的智慧:
通过这种分层缓存的机制,Spring既保证了单例Bean的唯一性,又巧妙地打破了循环依赖的死锁状态。理解这个机制不仅能帮助我们更好地使用Spring,更能够学习到框架设计中解决复杂问题的思路。