在软件开发中,循环依赖是指两个或多个组件相互引用形成的闭环关系。以Spring框架为例,当Bean A依赖Bean B,而Bean B又反过来依赖Bean A时,就构成了典型的循环依赖场景。这种相互依赖关系如果处理不当,会导致应用程序启动时出现无限递归调用,最终引发栈溢出错误(StackOverflowError)。
Spring框架作为Java生态中最主流的IoC容器,其核心功能之一就是管理Bean的创建和依赖注入。在传统的对象创建流程中,循环依赖会导致严重的初始化问题:假设Bean A需要先初始化完成才能注入到Bean B,而Bean B又需要先初始化完成才能注入到Bean A,这种"先有鸡还是先有蛋"的悖论使得常规的创建流程无法继续执行。
在Spring应用中可以观察到三种循环依赖模式:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
早期版本的Spring采用简单的"早期引用暴露"机制,即在对象实例化后立即将尚未初始化的原始引用暴露给其他Bean。但这种简单方案存在明显缺陷:当暴露的原始对象后续被AOP代理替换时,已经注入的引用仍然是原始对象,导致代理失效。
为解决这个问题,Spring发展出了成熟的三级缓存架构:
这种分层缓存设计的关键创新在于:不是直接暴露对象实例,而是通过ObjectFactory延迟获取引用。当需要解决循环依赖时,Spring会调用ObjectFactory的getObject()方法,这个方法会执行SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference()处理,确保在AOP代理场景下返回的是代理对象而非原始对象。
以一个简单的A→B→A循环依赖为例,Spring的处理过程可分为以下阶段:
这种机制成功打破了循环依赖的死锁状态,通过将对象的创建过程分为多个阶段(实例化→属性填充→初始化),并在适当时机暴露中间状态的对象引用来实现依赖闭环。值得注意的是,三级缓存中存储的ObjectFactory不仅解决了基本的循环依赖问题,更重要的是为AOP代理等扩展功能提供了处理空间,这是二级缓存无法实现的复杂场景。
在Spring框架中,三级缓存机制是解决循环依赖问题的核心设计。这一机制通过三个不同层级的缓存协同工作,确保在对象尚未完全初始化时就能被其他对象引用,同时保证最终注入的Bean是完整且符合预期的。理解三级缓存的工作原理,需要从它的结构定义、运作流程以及与AOP代理的交互三个维度展开。
Spring容器内部维护着三个关键缓存数据结构:
值得注意的是,三级缓存的设计并非简单的层级递进关系,而是各司其职的协作体系。一级缓存是终点站,二级缓存是临时中转站,而三级缓存则是生产车间。
以一个典型的A→B→A循环依赖场景为例,三级缓存的工作流程可分为以下关键步骤:
这个过程中,三级缓存的核心价值在于:通过ObjectFactory的延迟执行能力,在需要时才决定返回原始对象还是代理对象,解决了AOP代理与循环依赖的时序矛盾。
当循环依赖遇上AOP代理时,三级缓存展现出不可替代的价值。考虑以下关键点:
三级缓存机制展现了Spring框架在解决复杂问题时的精巧设计。它不仅解决了对象创建阶段的循环依赖问题,还通过ObjectFactory的抽象层,实现了与AOP等扩展功能的无缝集成。这种设计既保证了功能正确性,又维持了框架的扩展能力,是Spring IoC容器最核心的架构智慧之一。
在Spring框架处理循环依赖的核心机制中,ObjectFactory扮演着"延迟暴露"的关键角色。当两个Bean相互依赖时(例如ServiceA依赖ServiceB,同时ServiceB又依赖ServiceA),传统的实例化流程会陷入死锁状态。三级缓存中的第二级缓存——singletonFactories通过存储ObjectFactory对象,创造性地解决了这个经典难题。
ObjectFactory本质上是一个函数式接口,其核心方法是getObject()
,它并非直接存储Bean实例,而是保存了一个能够生成Bean引用的逻辑。这种设计实现了两个重要特性:
当Spring容器完成Bean的实例化(分配内存空间)但尚未进行属性注入时,会执行以下关键操作:
// 伪代码展示Spring核心逻辑
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
这个Lambda表达式就是ObjectFactory的具体实现,它将被存入singletonFactories缓存,为后续可能的循环依赖提供解决方案。
在典型的AB循环依赖场景中,ObjectFactory与三级缓存配合完成以下关键步骤:
初始曝光阶段:
实例化ServiceA后,将其ObjectFactory存入singletonFactories
此时缓存状态:
singletonFactories:{"serviceA": ObjectFactory<ServiceA>}
earlySingletonObjects:{}
singletonObjects:{}
依赖解析阶段:
当ServiceA需要注入ServiceB时,容器开始创建ServiceB
ServiceB实例化后,其ObjectFactory同样存入singletonFactories
缓存状态变为:
singletonFactories:{"serviceA": ObjectFactory<ServiceA>, "serviceB": ObjectFactory<ServiceB>}
earlySingletonObjects:{}
singletonObjects:{}
循环触发阶段:
getObject()
方法获取ServiceA的早期引用这个过程中,ObjectFactory的智能之处在于它并非简单地返回原始对象。通过getEarlyBeanReference()
方法,Spring会检查该Bean是否需要AOP代理,如果需要则提前生成代理对象,确保后续注入的都是一致的代理引用。
ObjectFactory对AOP代理的特殊处理体现在getEarlyBeanReference()
方法的实现中。该方法会遍历所有BeanPostProcessor,特别是AbstractAutoProxyCreator
,在以下两种情况下生成代理对象:
这种设计解决了"代理不一致"的经典问题。如果没有ObjectFactory的这层包装,可能会出现:
ObjectFactory的设计体现了Spring在性能与安全性之间的精巧平衡:
一个值得注意的实现细节是,当ObjectFactory完成使命后,Spring会立即执行清理操作:
// 从三级缓存移除
this.singletonFactories.remove(beanName);
// 将早期引用存入二级缓存
this.earlySingletonObjects.put(beanName, bean);
这种缓存升级机制既保证了后续依赖注入能直接获取对象,又避免了重复创建代理对象的开销。
在实际开发中,ObjectFactory相关的问题往往表现为以下形式:
通过理解ObjectFactory的工作原理,开发者可以更准确地诊断这类复杂问题。例如,当遇到"AOP增强方法不生效"的情况时,首先应该检查循环依赖中的Bean是否通过ObjectFactory正确生成了代理对象,而不是盲目地排查切面配置。
在Spring框架中,AOP代理与循环依赖的冲突是一个典型的"时序悖论"问题。当两个需要被AOP增强的Bean相互依赖时,常规的代理创建流程会被循环依赖打破,导致注入的可能是未经增强的原始对象。这种冲突的核心在于:AOP代理的标准创建时机(初始化完成后)与循环依赖要求的提前暴露时机(初始化完成前)存在根本性矛盾。
通过分析Spring的Bean生命周期可以发现,AbstractAutoProxyCreator作为AOP代理的核心处理器,其wrapIfNecessary方法通常只在postProcessAfterInitialization阶段执行。但在循环依赖场景下,当Bean A依赖Bean B,而Bean B又反过来依赖Bean A时,Spring会通过三级缓存提前暴露正在创建中的Bean A。此时若Bean A需要AOP增强,就会出现两种可能的问题场景:
参考CSDN博客中的案例,当ServiceA和ServiceB相互依赖且都需要事务增强时,可能出现ServiceA中注入的ServiceB未被事务增强的情况。这是因为ServiceB在注入到ServiceA时,可能还处于原始对象状态,而事务代理尚未生成。
Spring通过引入三级缓存中的ObjectFactory机制,创造性地解决了这一难题。具体流程表现为:
这种设计的关键优势在于将代理决策时机延迟到最后可能的时刻。如博客园某技术文章所述:“Spring并非在暴露时就创建代理,而是在其他Bean真正需要注入时才通过ObjectFactory.getObject()动态决定是否生成代理”。
在实际开发中,这种冲突常表现为以下现象:
以事务失效为例,当存在如下循环依赖时:
@Service
public class OrderService {
@Autowired
private UserService userService;
@Transactional
public void createOrder() {...}
}
@Service
public class UserService {
@Autowired
private OrderService orderService;
}
若处理不当,UserService中注入的OrderService可能是未被事务增强的原始对象,导致createOrder()方法的事务特性丢失。
Spring通过三级缓存与AbstractAutoProxyCreator的协作,建立了完整的解决方案:
源码层面,AbstractAutoProxyCreator的关键处理逻辑如下:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey); // 可能返回代理或原始对象
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
这种双重检查机制确保了无论Bean是通过常规初始化还是循环依赖提前暴露,最终获得的都是正确且唯一的代理实例。
为避免AOP代理与循环依赖的冲突问题,建议采用以下最佳实践:
特别值得注意的是,在Spring Boot 2.6+版本中,默认禁止了循环依赖。如需使用,必须显式配置spring.main.allow-circular-references=true。这一改变正是由于循环依赖与AOP等高级特性的兼容性问题难以完美解决。
在Spring框架的核心容器实现中,循环依赖的解决机制主要围绕DefaultSingletonBeanRegistry
类的三级缓存体系展开。通过跟踪AbstractAutowireCapableBeanFactory
的doCreateBean
方法调用链,我们可以完整还原Spring处理循环依赖的底层逻辑。
在DefaultSingletonBeanRegistry
类中定义了三个关键Map结构:
// 一级缓存:存储完全初始化后的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存储早期曝光的原始Bean对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存储ObjectFactory包装对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
createBeanInstance()
方法执行后,会通过addSingletonFactory()
将原始对象包装为ObjectFactory存入三级缓存:protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
}
populateBean()
进行属性注入时,如果发现依赖对象尚未创建,会触发getSingleton()
的递归调用。此时关键判断逻辑如下:protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
当存在AOP代理需求时,AbstractAutoProxyCreator
会介入处理流程。关键代码体现在getEarlyBeanReference()
方法:
protected Object getEarlyBeanReference(String beanName, Object bean) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
此时代理对象的生成时机被提前到三级缓存阶段,通过SmartInstantiationAwareBeanPostProcessor
接口的扩展点实现。在后续initializeBean()
阶段,会通过earlyProxyReferences
判断是否已生成代理对象,避免重复代理。
以A依赖B,B依赖A的循环场景为例:
getEarlyBeanReference()
生成A的代理对象earlyProxyReferences
发现已处理过代理,直接返回现有对象getEarlyBeanReference()
可能产生不同的代理对象synchronized (this.singletonObjects)
代码块内完成,既保证线程安全又避免过度锁竞争。
singletonsCurrentlyInCreation
等集合配合isSingletonCurrentlyInCreation()
方法,精确控制Bean的创建阶段状态。
在Spring框架的设计哲学中,循环依赖问题的解决方案堪称经典。通过三级缓存机制与早期对象曝光的巧妙结合,Spring不仅解决了对象创建过程中的死锁问题,更展现了框架设计者对复杂系统问题的深刻洞察。这种设计智慧体现在三个关键维度:分层治理、动态适配和风险隔离。
三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)的层级结构体现了分而治之的思想。当检测到循环依赖时,Spring并不试图一次性解决所有问题,而是通过ObjectFactory将半成品对象暂存于三级缓存,允许其他对象先行引用。这种"先搭骨架再填血肉"的做法,打破了传统对象构造必须一步到位的思维定式。正如腾讯云开发者社区案例所示,即使AService和BService相互注入,Spring也能通过阶段性完成对象构建来避免死锁。
ObjectFactory的延迟加载特性展现了Spring的动态适配能力。在AOP代理场景下,三级缓存存储的不是原始对象而是ObjectFactory,这使得框架能够在最终实例化时动态决定返回原始对象还是代理对象。CSDN技术博客中提到的案例证明,这种设计完美解决了代理对象与原始对象在循环依赖中的身份冲突问题——当Bean需要被代理时,ObjectFactory.getObject()会返回增强后的实例,而普通Bean则直接返回原始引用。
早期曝光与完整初始化的分离体现了防御性编程思想。Spring将对象曝光分为两个阶段:三级缓存存储工厂对象(可生成早期引用),二级缓存存储已处理代理的半成品,一级缓存存储完全初始化的成品。这种隔离机制确保在并发环境下,其他线程要么获得统一代理对象,要么等待对象完全初始化。某技术社区分析的源码显示,DefaultSingletonBeanRegistry中的getSingleton方法通过同步块与三级缓存检查的组合,实现了线程安全与性能的平衡。
值得注意的是,这种设计并非完美无缺。构造器注入的循环依赖仍无法解决,这反而成为Spring推崇设值注入的最佳实践佐证。框架设计者有意为之的约束,引导开发者遵循更合理的对象关联方式,体现了"约定优于配置"的深层设计哲学。
在AOP代理与循环依赖的冲突处理中,Spring通过AbstractAutoProxyCreator的postProcessAfterInitialization与三级缓存的协同,展示了横切逻辑与核心容器的无缝集成能力。当检测到当前Bean正在被循环依赖时,SmartInstantiationAwareBeanPostProcessor会提前触发代理生成,这种条件判断与流程跳转的精密配合,使得本应相互排斥的两种机制产生了化学反应般的协同效应。
[1] : https://blog.csdn.net/qq_43290318/article/details/114679612
[2] : https://www.cnblogs.com/oneokrock-373/p/18965981
[3] : https://juejin.cn/post/7477883001449005091
[4] : https://developer.aliyun.com/article/1436029