三级缓存与循环依赖
- 其他
- 10天前
- 13热度
- 0评论
三级缓存与循环依赖详解
在Spring框架中,三级缓存机制是一种重要的设计模式,用于解决循环依赖和动态代理等问题。本文将详细介绍三级缓存的职责划分、为什么需要三级缓存、循环依赖如何被解除以及动态代理介入时的情况。
1. 三级缓存的职责划分
一级缓存 singletonObjects
- 定义:存放已经完全初始化完成的对象。
- 作用:确保每个单例对象在容器中只创建一次,并且可以快速访问。
二级缓存 earlySingletonObjects
- 定义:存放提前暴露的早期引用,这些引用可能是原始对象也可能是代理对象。
- 作用:解决循环依赖问题,在一级缓存未完成初始化的情况下提供对象引用。
三级缓存 singletonFactories
- 定义:存放 ObjectFactory 对象工厂实例,用于按需生成早期引用。
- 作用:延迟创建对象,确保在需要时能够动态地生成代理对象。
2. 为什么需要三级缓存
解决循环依赖问题
如果只有一级缓存,在遇到循环依赖的情况下(例如 A -> B -> A),无法提前获取到完整初始化的对象,导致注入失败。通过引入二级和三级缓存,可以在对象尚未完全初始化时提供一个早期引用。
支持动态代理
- 关键点:在创建对象的过程中,如果需要生成代理对象,可以通过三级缓存中的 ObjectFactory 机制延迟创建。
- 实现细节:当B依赖于A时,在A的工厂方法中可以预先创建代理对象,并将其注入到B。
3. 循环依赖如何被解除
具体步骤
- 创建Bean A,但尚未完全初始化。将 ObjectFactory 放入三级缓存。
- Bean A 发现其依赖于 B,因此转而创建 B。
- 在创建 B 的过程中,B 又发现它依赖于 A。此时会依次查询一级、二级和三级缓存。
- 如果在三级缓存中找到 ObjectFactory,则调用工厂方法生成早期引用。
- 将这个早期引用放入二级缓存,并注入到 Bean B 中。
- 当 B 完成初始化后,再回到 A 的创建过程并完成依赖注入。
关键点
- 三级缓存:确保在B需要A时可以通过 ObjectFactory 生成一个早期引用。
- 延迟代理对象的创建:保证最终暴露给容器的对象是经过动态代理后的实例。
4. 动态代理介入时发生什么
当 A 需要被自动代理器包装成代理类(例如 CGLIB 代理)时:
- 在 getEarlyBeanReference 方法中,如果 B 反向依赖于 A,则可以生成代理对象。
- 这意味着注入到 B 中的 A 实际上可能是代理对象。
- 确保在最后一步将原始对象与代理对象一致化:容器会对齐“最终暴露对象”,确保一级缓存中的实例是同一个代理实例。
示例代码
public Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object earlyInstance) {
// 自动创建 A 的代理对象并注入到 B 中
return new ProxyCreator().createProxy(earlyInstance);
}5. 实现与验证要点
观察计数器验证
- 新增了可观测 Advice(计数器)用于验证代理拦截是否实际生效。
- 关键断言:
- b.getA() == a:确保注入对象与容器最终对象一致。
- a instanceof ProxyObject:确认 A 是代理对象。
- 调用切点方法后 Advice 计数增加,验证拦截效果。
测试配置
循环依赖 + 自动代理 XML 配置:
<bean id="A" class="com.example.A"> <property name="bDependency" ref="B"/> </bean> <bean id="B" class="com.example.B"> <property name="aDependency" ref="A"/> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>测试代码:
@Test public void testCircularDependencyWithProxy() { ApplicationContext context = new ClassPathXmlApplicationContext("config.xml"); A aBean = (A) context.getBean("A"); B bBean = (B) context.getBean("B"); assertNotNull(aBean); assertNotNull(bBean); assertTrue(aBean instanceof ProxyObject); // 验证代理对象 }
总结
三级缓存机制在Spring框架中扮演着关键角色,通过合理划分职责和延迟创建策略解决了循环依赖问题,并支持动态代理的无缝集成。理解这些细节有助于开发者更好地利用 Spring 框架的强大功能。
实践建议:
- 在设计复杂依赖关系时考虑使用三级缓存机制解决循环引用。
- 利用Spring AOP特性进行方法拦截,确保业务逻辑的高效实现和维护性。
- 通过详细的单元测试验证代理对象和循环依赖场景下的正确行为。