AI摘要

文章揭示:Spring三级缓存的核心并非解决循环依赖,而是为AOP代理提供“延迟创建、一次代理、全局一致”的机制;循环依赖只是副产品。通过源码与案例说明,无三级缓存时早期代理会破坏依赖链,而ObjectFactory把代理时机推迟到真正需要时,确保所有引用同一代理对象,并避免重复代理。
我曾经以为三级缓存就是Spring解决循环依赖的"银弹",直到一次生产环境的Bean创建失败让我意识到,这套机制背后隐藏着更深刻的设计哲学。

记得在重构一个大型微服务模块时,我们遇到了一个诡异的启动错误:BeanCurrentlyInCreationException。奇怪的是,这个错误只在特定条件下出现,而且涉及的Bean明明是通过setter注入,理论上应该能被三级缓存解决。这次排查让我重新审视了Spring的三级缓存机制,也让我明白了它背后的真正价值。

一、从一个诡异的Bean创建异常说起

先来看看当时遇到问题的代码结构:

@Service
public class ServiceA {
    private ServiceB serviceB;
    
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
        System.out.println("ServiceB注入完成");
    }
    
    @PostConstruct
    public void init() {
        System.out.println("ServiceA初始化完成");
    }
}

@Service  
public class ServiceB {
    private ServiceC serviceC;
    
    @Autowired
    public void setServiceC(ServiceC serviceC) {
        this.serviceC = serviceC;
        System.out.println("ServiceC注入完成");
    }
}

@Service
public class ServiceC {
    private ServiceA serviceA;  // 循环依赖!
    
    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
        System.out.println("ServiceA注入完成");
    }
}

理论上,这种setter注入的循环依赖应该能被Spring正常处理。但在加入AOP代理后,系统却抛出了异常。这让我开始怀疑:三级缓存真的只是为循环依赖而设计的吗?

二、循环依赖的本质:为什么这是个难题?

2.1 循环依赖的三种类型

// 1. 构造器循环依赖 - Spring无法解决
@Service
public class ConstructorCycleA {
    private final ConstructorCycleB b;
    public ConstructorCycleA(ConstructorCycleB b) { this.b = b; }
}

@Service
public class ConstructorCycleB {
    private final ConstructorCycleA a;
    public ConstructorCycleB(ConstructorCycleA a) { this.a = a; }
}

// 2. Setter循环依赖 - Spring可以解决  
@Service
public class SetterCycleA {
    private SetterCycleB b;
    @Autowired public void setB(SetterCycleB b) { this.b = b; }
}

// 3. 字段注入循环依赖 - Spring可以解决
@Service
public class FieldCycleA {
    @Autowired private FieldCycleB b;
}

2.2 循环依赖的根本矛盾

循环依赖的核心问题是​初始化顺序的悖论​:

Bean A的创建需要Bean B,但Bean B的创建又需要Bean A。这就好比鸡生蛋还是蛋生鸡的问题。

在没有特殊机制的情况下,这种相互依赖会导致死锁。

三、三级缓存的全景解析:不只是循环依赖的解决方案

3.1 三级缓存的结构定义

让我们深入Spring源码,看看三级缓存的真实面貌:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry {
    
    // 第一级缓存:存放完整的单例Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 第二级缓存:存放早期的Bean引用(半成品)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 第三级缓存:存放Bean工厂,用于创建早期引用
    private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
    
    // 正在创建中的Bean名称集合
    private final Set<String> singletonsCurrentlyInCreation = 
        Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

3.2 三级缓存的工作流程

通过一个具体的例子来理解三级缓存如何协作:

@Component
public class UserService {
    @Autowired private OrderService orderService;
}

@Component  
public class OrderService {
    @Autowired private UserService userService;
}

创建流程分解​:

  1. 开始创建UserService

    // 1. 标记为创建中
    singletonsCurrentlyInCreation.add("userService");
    
    // 2. 实例化(调用构造器)
    UserService userService = new UserService();
    
    // 3. 将Bean工厂放入三级缓存
    addSingletonFactory("userService", () -> getEarlyBeanReference("userService", userService));
  2. 属性注入时发现依赖OrderService

    // 1. 查找orderService,发现不存在
    // 2. 开始创建OrderService
  3. 创建OrderService流程

    // 1. 标记orderService为创建中
    singletonsCurrentlyInCreation.add("orderService");
    
    // 2. 实例化OrderService
    OrderService orderService = new OrderService();
    
    // 3. 将OrderService工厂放入三级缓存
    addSingletonFactory("orderService", () -> getEarlyBeanReference("orderService", orderService));
    
    // 4. 属性注入时发现需要UserService
  4. 解决循环依赖的关键时刻

    // 1. 在三级缓存中找到了UserService的ObjectFactory
    ObjectFactory<?> singletonFactory = this.singletonFactories.get("userService");
    
    // 2. 执行工厂方法获取早期引用
    Object earlyReference = singletonFactory.getObject();
    
    // 3. 将早期引用放入二级缓存,从三级缓存移除
    this.earlySingletonObjects.put("userService", earlyReference);
    this.singletonFactories.remove("userService");
    
    // 4. 将早期引用注入到OrderService

四、三级缓存的真正价值:AOP代理的优雅处理

现在来回答核心问题:三级缓存真的只是为了解决循环依赖吗?

答案是否定的。三级缓存的设计更多是为了​优雅地处理AOP代理​。

4.1 如果没有三级缓存,只有二级缓存会怎样?

假设我们只有二级缓存,看看AOP代理场景下的问题:

@Service
public class BusinessService {
    @Autowired private AnotherService anotherService;
    
    public void businessMethod() {
        // 业务方法
    }
}

@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.BusinessService.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        // 日志逻辑
        return joinPoint.proceed();
    }
}

在这种场景下,如果只有二级缓存:

  1. BusinessService实例化后,直接创建代理对象放入二级缓存
  2. 但当需要注入其他依赖时,可能代理对象的初始化还不完整
  3. 如果代理逻辑依赖其他Bean,就会出现问题

4.2 三级缓存如何优雅解决AOP问题

三级缓存通过ObjectFactory延迟代理对象的创建:

protected Object getEarlyBeanReference(String beanName, Object bean) {
    Object exposedObject = bean;
    if (!singletonsCurrentlyInCreation.contains(beanName)) {
        return exposedObject;
    }
    
    // 关键:如果有必要,创建早期代理引用
    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessors()) {
        exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        if (exposedObject == null) {
            return exposedObject;
        }
    }
    return exposedObject;
}

这样设计的好处​:

  1. 延迟代理创建​:只有在真正需要早期引用时才创建代理
  2. 保证代理一致性​:无论从哪个路径获取Bean,得到的都是同一个代理对象
  3. 避免重复代理​:防止同一个Bean被多次代理

五、从源码角度验证:AbstractAutowireCapableBeanFactory的创建逻辑

让我们深入Spring源码的关键方法:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // 1. 实例化Bean
    Object beanInstance = instantiateBean(beanName, mbd);
    
    // 2. 将Bean包装到ObjectFactory中,添加到三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
    
    // 3. 属性注入(可能触发循环依赖)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    
    // 5. 处理循环依赖的早期引用
    if (earlySingletonExposure) {
        // 检查早期引用是否已经被使用
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 如果初始化后的Bean与早期引用相同,直接返回
            if (exposedObject == beanInstance) {
                exposedObject = earlySingletonReference;
            }
            // 处理AOP代理的特殊情况
            else if (!this.allowRawInjectionDespiteWrapping &&
                    hasDependentBean(beanName)) {
                // 验证依赖关系
            }
        }
    }
    
    return exposedObject;
}

六、实际案例:三级缓存解决AOP代理的循环依赖

通过一个真实案例来理解三级缓存的价值:

@Service
@Transactional
public class TransactionalService {
    @Autowired private AsyncService asyncService;
    
    public void doSomething() {
        // 事务操作
    }
}

@Service  
public class AsyncService {
    @Autowired private TransactionalService transactionalService;
    
    @Async
    public void asyncMethod() {
        transactionalService.doSomething(); // 这里需要的是代理对象!
    }
}

@Aspect
@Component
public class MonitoringAspect {
    @Around("execution(* com.example.*.*(..))")
    public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
        // 监控逻辑
        return joinPoint.proceed();
    }
}

在这个复杂的场景中:

  1. TransactionalService需要被事务代理
  2. AsyncService需要被异步代理
  3. 两者存在循环依赖
  4. 同时还有全局的监控Aspect

三级缓存确保每个Bean只被代理一次,且所有依赖方得到的是正确的代理对象。

七、循环依赖的局限性:什么时候三级缓存也无能为力

7.1 构造器注入的循环依赖

@Service
public class ConstructorA {
    private final ConstructorB b;
    
    @Autowired
    public ConstructorA(ConstructorB b) {  // 无法解决!
        this.b = b;
    }
}

@Service
public class ConstructorB {
    private final ConstructorA a;
    
    @Autowired
    public ConstructorB(ConstructorA a) {  // 无法解决!
        this.a = a;
    }
}

原因​​:构造器注入在实例化阶段就需要依赖对象,而此时Bean还没有被创建,无法放入三级缓存。

7.2 多例(Prototype)作用域的循环依赖

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeA {
    @Autowired private PrototypeB b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  
@Component
public class PrototypeB {
    @Autowired private PrototypeA a;
}

原因​​:多例Bean不缓存,每次都会重新创建,三级缓存机制不适用。

7.3 @Async方法的特殊限制

@Service
public class AsyncServiceA {
    @Autowired private AsyncServiceB b;
    
    @Async
    public void asyncMethod() {
        b.anotherAsyncMethod();
    }
}

@Service
public class AsyncServiceB {
    @Autowired private AsyncServiceA a;  // 可能有问题!
    
    @Async
    public void anotherAsyncMethod() {
        a.asyncMethod();
    }
}

八、最佳实践:避免和解决循环依赖的策略

8.1 设计层面的预防

// 不好的设计:双向紧密耦合
@Service
public class TightCouplingA {
    @Autowired private TightCouplingB b;
}

@Service  
public class TightCouplingB {
    @Autowired private TightCouplingA a;
}

// 好的设计:通过接口解耦
public interface ServiceA {
    void methodA();
}

public interface ServiceB {
    void methodB();  
}

@Service
public class ServiceAImpl implements ServiceA {
    // 只依赖接口,不依赖具体实现
    private final ServiceB serviceB;
    
    public ServiceAImpl(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

8.2 使用@Lazy延迟注入

@Service
public class LazyServiceA {
    // 延迟注入,打破循环依赖
    @Lazy
    @Autowired
    private LazyServiceB b;
}

@Service
public class LazyServiceB {
    @Autowired
    private LazyServiceA a;
}

8.3 应用事件解耦

@Service
public class EventDrivenA {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void doSomething() {
        // 发布事件而不是直接调用
        eventPublisher.publishEvent(new SomethingDoneEvent(this));
    }
}

@Component
public class EventDrivenB {
    @EventListener
    public void handleEvent(SomethingDoneEvent event) {
        // 响应事件
    }
}

九、深度排查:循环依赖问题的调试技巧

9.1 使用Spring的调试支持

# application.properties
# 启用循环依赖详细日志
logging.level.org.springframework.beans=DEBUG
# 显示Bean创建过程
debug=true

9.2 自定义BeanPostProcessor进行调试

@Component
public class CycleDependencyDebugProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean初始化完成: " + beanName);
        return bean;
    }
}

9.3 使用BeanFactory获取缓存状态

@Service
public class CacheInspector {
    
    @Autowired
    private ConfigurableListableBeanFactory beanFactory;
    
    public void inspectCaches() {
        if (beanFactory instanceof DefaultSingletonBeanRegistry) {
            DefaultSingletonBeanRegistry registry = 
                (DefaultSingletonBeanRegistry) beanFactory;
            
            // 通过反射查看缓存状态(生产环境慎用)
            // 这里可以查看各级缓存的当前状态
        }
    }
}

总结

经过这次深入的探究,我认识到三级缓存机制的设计远比表面看起来的精妙:

  1. 主要目的不是循环依赖​:三级缓存的核心价值是优雅处理AOP代理,循环依赖只是其应用场景之一
  2. 保证代理一致性​:确保在整个Bean生命周期中,无论何时获取Bean,得到的都是同一个代理实例
  3. 解决时序问题​:通过延迟代理创建,解决了Bean初始化顺序与代理创建的矛盾

真正的启示​:Spring框架的很多设计都是多维度的,表面解决一个问题,实则考虑了框架整体的扩展性、一致性和性能。

这次经历让我明白,理解一个框架不能停留在API层面,而要深入其设计哲学。三级缓存教会我的不是如何解决循环依赖,而是​如何通过巧妙的设计在复杂约束条件下保持系统的优雅和健壮​。

这种理解在后续学习Spring其他模块(如Spring Cloud、Spring Data)时给了我很大的帮助,让我能够更快地掌握复杂机制背后的核心思想。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:Spring循环依赖的真相:三级缓存真的只是为了解决循环依赖吗?
▶ 本文链接:https://www.huangleicole.com/backend-related/49.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏