Seal^_^ 2025-11-19 11:21 采纳率: 72.6%
浏览 8
已结题

Spring是如何解决循环依赖问题的?(三级缓存:singletonObjects,earlySingletonObjects,singletonFactories)为什么构造器注入无法解决循环依赖?

Spring是如何解决循环依赖问题的?(三级缓存:singletonObjects,earlySingletonObjects,singletonFactories)为什么构造器注入无法解决循环依赖?

  • 写回答

2条回答 默认 最新

  • 紫雾凌寒 新星创作者: 人工智能技术领域 2025-11-19 11:32
    关注

    让 “紫雾凌寒” 来帮你回答,本回答结合 DeepSeek

    如果还有问题,请评论或点击头像私信,如果有帮助到您, 请关注、采纳、点赞

    1. Spring解决循环依赖的方式(基于三级缓存)
      - 关键点
      • Spring通过三级缓存来解决循环依赖问题。这三级缓存分别是singletonObjects(存放完整的单例对象)、earlySingletonObjects(存放提前暴露的单例对象)、singletonFactories(存放单例对象的工厂)。
      • 解决方案
      • 当一个bean被创建时,首先会将其创建过程中的工厂对象放入singletonFactories中。
      • 然后,如果有其他bean需要依赖这个正在创建的bean,会从singletonFactories中获取对应的工厂对象来获取一个提前暴露的单例对象(可能是一个不完整的对象)。
      • 当这个bean创建完成后,会将其从singletonFactories中移除,并放入singletonObjects中。同时,如果在创建过程中允许提前暴露单例,也会放入earlySingletonObjects中。
      • 示例代码如下(简化的Spring创建bean的逻辑示意):
    // 三级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private final Map<String, Object> singletonFactories = new ConcurrentHashMap<>();
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    
    // 创建bean的方法
    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
        // 创建bean的实例
        Object bean = createBeanInstance(mbd, beanName, args);
        // 将创建bean的工厂对象放入singletonFactories
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        // 属性填充等后续操作
        populateBean(beanName, mbd, bean);
        // 初始化bean
        bean = initializeBean(beanName, bean, mbd);
        // 将完整的bean放入singletonObjects
        addSingleton(beanName, bean);
        return bean;
    }
    
    // 添加单例工厂
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        singletonFactories.put(beanName, singletonFactory);
    }
    
    // 获取提前暴露的bean引用
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
    
    // 添加单例对象
    protected void addSingleton(String beanName, Object singletonObject) {
        singletonObjects.put(beanName, singletonObject);
        singletonFactories.remove(beanName);
        earlySingletonObjects.remove(beanName);    
    }
    
    • 三级缓存的作用
      • singletonFactories用于存放创建bean的工厂对象,这样在其他bean依赖该bean时,可以通过工厂获取一个提前暴露的对象(可能是不完整的),从而避免循环依赖。
      • earlySingletonObjects用于存放提前暴露的单例对象,在bean创建完成但还未完全初始化时可以获取到。
      • singletonObjects用于存放完整的单例对象。
        2. 构造器注入无法解决循环依赖的原因
    • 关键点
      • 构造器注入是在对象创建时就完成依赖注入,而Spring解决循环依赖是基于对象创建过程中的提前暴露和引用机制。
    • 原因
      • 当使用构造器注入时,对象在构造器执行时就需要完整地获取其所有依赖。如果存在循环依赖,在创建第一个依赖对象时就会因为需要第二个依赖对象(而第二个依赖对象又需要第一个依赖对象)而陷入死循环,无法完成创建。
      • 例如:
    @Component
    class A {
        private B b;
        public A(B b) {
            this.b = b;
        }
    }
    
    @Component
    class B {
        private A a;
        public B(A a) {
            this.a = a;
        }
    }
    
     - 在创建`A`的实例时,需要传入`B`的实例,而创建`B`的实例时又需要传入`A`的实例,这就导致无法创建出任何一个对象。
    
    1. 不同解决方案的优缺点
      - 基于三级缓存的解决方案
      • 优点
      • 能够很好地解决Spring框架中大部分的循环依赖问题,保证了Spring容器的正常运行。
      • 实现方式相对优雅,在Spring的核心逻辑中通过巧妙的设计解决了复杂的循环依赖场景。
      • 缺点
      • 实现较为复杂,需要理解Spring的对象创建流程和缓存机制。
      • 对于一些特殊的循环依赖场景可能需要额外的处理,比如在bean创建过程中涉及复杂的代理创建等情况。
      • 无其他解决方案(针对构造器注入)
      • 优点:无。
      • 缺点:构造器注入本身无法解决循环依赖问题,在设计依赖关系时需要特别注意避免这种情况,否则会导致程序无法正常运行。
    2. 总结
      - Spring通过三级缓存singletonObjectsearlySingletonObjectssingletonFactories有效地解决了大部分循环依赖问题。在bean创建过程中,利用工厂对象提前暴露不完整的单例对象,使得依赖该bean的其他bean能够正常创建,待bean创建完成后再放入完整的缓存中。而构造器注入由于在对象创建时就要求完整的依赖,所以无法解决循环依赖问题,在设计依赖关系时需要谨慎避免。

    希望以上解答对您有所帮助。如果您有任何疑问,欢迎在评论区提出。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

问题事件

  • 系统已结题 11月27日
  • 已采纳回答 11月19日
  • 创建了问题 11月19日