三级缓存与循环依赖

三级缓存与循环依赖详解

在Spring框架中,三级缓存机制是一种重要的设计模式,用于解决循环依赖和动态代理等问题。本文将详细介绍三级缓存的职责划分、为什么需要三级缓存、循环依赖如何被解除以及动态代理介入时的情况。

1. 三级缓存的职责划分

一级缓存 singletonObjects

  • 定义:存放已经完全初始化完成的对象。
  • 作用:确保每个单例对象在容器中只创建一次,并且可以快速访问。

二级缓存 earlySingletonObjects

  • 定义:存放提前暴露的早期引用,这些引用可能是原始对象也可能是代理对象。
  • 作用:解决循环依赖问题,在一级缓存未完成初始化的情况下提供对象引用。

三级缓存 singletonFactories

  • 定义:存放 ObjectFactory 对象工厂实例,用于按需生成早期引用。
  • 作用:延迟创建对象,确保在需要时能够动态地生成代理对象。

2. 为什么需要三级缓存

解决循环依赖问题

如果只有一级缓存,在遇到循环依赖的情况下(例如 A -> B -> A),无法提前获取到完整初始化的对象,导致注入失败。通过引入二级和三级缓存,可以在对象尚未完全初始化时提供一个早期引用。

支持动态代理

  • 关键点:在创建对象的过程中,如果需要生成代理对象,可以通过三级缓存中的 ObjectFactory 机制延迟创建。
  • 实现细节:当B依赖于A时,在A的工厂方法中可以预先创建代理对象,并将其注入到B。

3. 循环依赖如何被解除

具体步骤

  1. 创建Bean A,但尚未完全初始化。将 ObjectFactory 放入三级缓存。
  2. Bean A 发现其依赖于 B,因此转而创建 B。
  3. 在创建 B 的过程中,B 又发现它依赖于 A。此时会依次查询一级、二级和三级缓存。
  4. 如果在三级缓存中找到 ObjectFactory,则调用工厂方法生成早期引用。
  5. 将这个早期引用放入二级缓存,并注入到 Bean B 中。
  6. 当 B 完成初始化后,再回到 A 的创建过程并完成依赖注入。

关键点

  • 三级缓存:确保在B需要A时可以通过 ObjectFactory 生成一个早期引用。
  • 延迟代理对象的创建:保证最终暴露给容器的对象是经过动态代理后的实例。

4. 动态代理介入时发生什么

当 A 需要被自动代理器包装成代理类(例如 CGLIB 代理)时:

  1. 在 getEarlyBeanReference 方法中,如果 B 反向依赖于 A,则可以生成代理对象。
  2. 这意味着注入到 B 中的 A 实际上可能是代理对象。
  3. 确保在最后一步将原始对象与代理对象一致化:容器会对齐“最终暴露对象”,确保一级缓存中的实例是同一个代理实例。

示例代码

public Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object earlyInstance) {
    // 自动创建 A 的代理对象并注入到 B 中
    return new ProxyCreator().createProxy(earlyInstance);
}

5. 实现与验证要点

观察计数器验证

  1. 新增了可观测 Advice(计数器)用于验证代理拦截是否实际生效。
  2. 关键断言
    • 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特性进行方法拦截,确保业务逻辑的高效实现和维护性。
  • 通过详细的单元测试验证代理对象和循环依赖场景下的正确行为。