一、循环依赖的典型场景与挑战
在Spring框架中,循环依赖是开发者经常遇到的依赖注入问题。当两个或多个Bean相互引用时,就会形成循环依赖链。常规的setter注入或字段注入场景下,Spring通过提前暴露对象引用(Early Reference)机制可以解决大部分循环依赖问题。但当动态代理介入时,情况会变得复杂。
考虑以下典型场景:
@Servicepublic class OrderService {@Autowiredprivate PaymentService paymentService;@Asyncpublic void processOrder() {// 异步处理逻辑}}@Servicepublic class PaymentService {@Autowiredprivate OrderService orderService;public void handlePayment() {orderService.processOrder();}}
这个场景中存在两个关键特征:
- 双向依赖:OrderService与PaymentService相互引用
- 动态代理需求:OrderService的processOrder方法带有@Async注解,需要生成代理对象
当Spring容器初始化时,会按以下流程处理:
- 创建OrderService原始对象(未初始化属性)
- 发现依赖PaymentService,开始创建PaymentService
- PaymentService又依赖OrderService,此时容器中已有OrderService的原始对象
- 在常规场景下,此时会直接注入OrderService原始对象
- 但由于@Async的存在,需要为OrderService生成代理对象
二、动态代理带来的核心矛盾
在AOP场景下,Spring需要为带有注解的方法创建代理对象。代理对象的创建时机与普通Bean存在本质差异:
- 代理创建时机:必须在所有依赖注入完成后才能生成代理对象(需要完整的Bean定义)
- 循环依赖困境:若在依赖注入阶段就创建代理,会导致代理对象引用不完整;若延迟创建,又会破坏循环依赖的解决机制
这种矛盾在JDK动态代理和CGLIB代理中普遍存在。以@Async为例,Spring会:
- 创建目标Bean的原始对象
- 完成所有属性注入
- 基于完整对象创建代理(JDK Proxy或CGLIB)
- 将代理对象放入容器
三、三级缓存的精妙设计
为解决这个矛盾,Spring设计了三级缓存机制(Singleton Objects → Early Singleton Objects → Singleton Factories),其核心思想是:
- 一级缓存(Singleton Objects):存储完全初始化好的Bean
- 二级缓存(Early Singleton Objects):存储原始对象(未代理)的早期引用
- 三级缓存(Singleton Factories):存储ObjectFactory对象,用于按需生成代理
具体处理流程如下:
graph TDA[开始创建OrderService] --> B[创建原始对象放入三级缓存]B --> C[依赖注入PaymentService]C --> D[创建PaymentService]D --> E[PaymentService依赖OrderService]E --> F[从三级缓存获取OrderService工厂]F --> G[工厂创建原始对象早期引用]G --> H[将引用存入二级缓存]H --> I[完成PaymentService初始化]I --> J[返回OrderService继续初始化]J --> K[发现@Async需要代理]K --> L[从二级缓存获取早期引用]L --> M[基于完整对象创建代理]M --> N[将代理存入一级缓存]
关键点解析:
- 工厂模式的应用:三级缓存存储的是ObjectFactory,在真正需要时才创建对象,为代理创建保留了可能性
- 缓存降级机制:当检测到需要代理时,会从二级缓存获取原始对象,生成代理后存入一级缓存
- 引用一致性保证:通过缓存层级设计,确保所有依赖方获取到的是相同对象(原始对象或代理对象)
四、为什么必须使用三级缓存?
对比二级缓存方案,三级缓存的优势体现在:
- 延迟代理创建:只有在真正需要代理时才创建,避免不必要的代理对象生成
- 支持多种AOP场景:可兼容@Transactional、@Cacheable等多种需要代理的注解
- 内存优化:避免为所有可能被代理的对象提前创建代理实例
考虑以下极端场景:
@Servicepublic class ServiceA {@Autowiredprivate ServiceB serviceB;@Transactionalpublic void methodA() {}}@Servicepublic class ServiceB {@Autowiredprivate ServiceA serviceA;@Asyncpublic void methodB() {}}
在这个场景中:
- ServiceA需要事务代理
- ServiceB需要异步代理
- 两者相互依赖
三级缓存机制可以确保:
- 正确识别每个Bean需要的代理类型
- 在最后阶段统一生成所有必要的代理
- 保证所有依赖方获取到的是最终代理对象
五、最佳实践与注意事项
- 避免复杂循环依赖:虽然Spring可以解决,但复杂依赖会降低系统可维护性
- 合理使用代理注解:仅在必要方法上添加@Async等注解,减少代理创建开销
- 理解初始化顺序:可通过实现InitializingBean接口或@PostConstruct注解方法观察初始化流程
- 监控代理创建:在生产环境中,可通过Spring Boot Actuator监控Bean创建情况
对于必须存在的循环依赖,建议:
- 使用setter注入替代字段注入,提高可测试性
- 将共享逻辑提取到第三个Service中
- 考虑使用ApplicationContextAware获取Bean引用(谨慎使用)
六、总结
Spring的三级缓存机制是解决动态代理场景下循环依赖的精妙设计,它通过:
- 分层缓存策略平衡了性能与灵活性
- 工厂模式延迟了代理创建时机
- 严格的缓存降级流程保证了对象一致性
理解这个机制不仅有助于解决实际问题,更能帮助开发者深入掌握Spring容器的工作原理。在实际开发中,合理设计Bean依赖关系,避免不必要的复杂循环依赖,仍然是构建高可维护性系统的关键。