Spring三级缓存机制解析:循环依赖的优雅解决方案

Spring三级缓存机制解析:循环依赖的优雅解决方案
1. 循环依赖的本质与Spring的困境在Spring框架中循环依赖指的是两个或多个Bean相互依赖对方完成初始化的情况。比如Bean A的构造需要注入Bean B而Bean B的构造又需要注入Bean A。这种鸡生蛋蛋生鸡的问题在传统依赖注入模式下会导致无限递归最终抛出BeanCurrentlyInCreationException。Spring早期版本2.x之前对这种场景束手无策开发者只能通过重构代码来打破循环引用。但随着业务系统复杂度提升完全避免循环依赖变得越来越困难。比如在领域驱动设计中订单(Order)需要关联客户(Customer)而客户又需要维护自己的订单列表这种双向关联在领域模型中非常自然。关键点循环依赖本身不是编码错误而是对象关系的一种合理表达。Spring需要提供机制来支持这种合理需求。2. 三级缓存的设计架构解析Spring通过三级缓存机制优雅地解决了这个问题。这三个缓存分别存储在DefaultSingletonBeanRegistry类中// 一级缓存存放完全初始化好的Bean private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存存放早期暴露的Bean已实例化但未初始化 private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存存放Bean工厂对象用于处理AOP代理 private final MapString, ObjectFactory? singletonFactories new HashMap(16);它们的协作流程可以类比为生产线当Bean开始创建时首先在三级缓存注册ObjectFactory遇到依赖注入时会依次检查各级缓存最终完成初始化的Bean会被提升到一级缓存3. 完整解决流程的步骤拆解让我们通过OrderService和UserService的循环依赖案例看看三级缓存如何运作3.1 实例化阶段开始创建OrderService通过反射调用构造函数实例化对象此时属性都是null将原始对象包装成ObjectFactory放入三级缓存开始属性注入发现需要UserService3.2 依赖解决阶段开始创建UserService同样实例化后放入三级缓存注入属性时发现需要OrderService从三级缓存获取OrderService的ObjectFactory执行getObject()获取早期引用可能经过AOP处理将OrderService的引用注入UserService完成UserService的初始化移入一级缓存3.3 最终初始化回到OrderService的属性注入此时可以直接从一级缓存获取完整的UserService完成OrderService的初始化移入一级缓存清除下级缓存中的相关条目这个过程中二级缓存earlySingletonObjects主要起到性能优化作用避免重复执行ObjectFactory的getObject()方法。4. AOP代理的特殊处理机制当涉及AOP代理时情况会变得更复杂。假设OrderService需要被JDK动态代理// 在ObjectFactory中处理的逻辑 protected Object getEarlyBeanReference(String beanName, Object bean) { // 如果需要代理在这里创建代理对象 if (!earlyProxyReferences.contains(beanName) isSingletonCurrentlyInCreation(beanName)) { return wrapIfNecessary(bean, beanName); } return bean; }三级缓存存放ObjectFactory而非直接存原始对象就是为了在获取早期引用时有机会介入AOP处理。这种设计保证了最终注入的总是代理对象避免重复创建代理保持代理对象的单例特性5. 典型问题排查与解决方案5.1 构造器注入失效问题三级缓存只能解决属性注入(setter注入)的循环依赖。如果使用构造器注入在实例化阶段就需要完整依赖此时缓存机制无法介入。解决方案改为属性注入使用Lazy延迟加载重构代码消除循环5.2 原型Bean的循环依赖原型(prototype)作用域的Bean无法使用三级缓存因为Spring不缓存原型Bean的实例每次注入都需要创建新对象 解决方案只能是重构代码结构。5.3 自我依赖检测Spring会通过inCreationCheckExclusions集合检测自我依赖// AbstractBeanFactory if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }6. 性能优化与实现细节6.1 缓存访问顺序优化Spring在获取Bean时采用层级检查策略先查一级缓存singletonObjects完全初始化的Bean再查二级缓存earlySingletonObjects早期引用最后查三级缓存singletonFactories工厂对象这种设计减少了同步锁的竞争因为一级缓存是ConcurrentHashMap而二级缓存只是HashMap。6.2 并发控制机制使用singletonsCurrentlyInCreation集合记录正在创建的Bean// 使用ThreadLocal避免并发问题 private final ThreadLocalObject prototypesCurrentlyInCreation new NamedThreadLocal(Prototype beans currently in creation);6.3 内存占用控制三级缓存不会无限增长因为完成初始化的Bean会立即从下级缓存移除正常情况下缓存数量≈最大依赖深度采用软引用/弱引用策略防止内存泄漏在实际项目中我曾遇到过一个深度达7层的循环依赖链。通过分析发现是领域模型设计不合理导致的最终通过引入DTO打破了部分循环。这也提醒我们虽然Spring提供了解决方案但过度依赖这个特性可能意味着设计需要优化。