面试题:Spring 如何解决循环依赖?

Spring 通过三级缓存机制来解决循环依赖问题。以下是 Spring 解决循环依赖的详细过程:


1. 三级缓存的作用

Spring 使用三级缓存来管理 Bean 的创建和依赖注入:

  1. Singleton Objects Cache(一级缓存)
    存放完全初始化好的单例 Bean。
    Key: Bean 名称,Value: 完全初始化的 Bean 实例。
  2. Early Singleton Objects Cache(二级缓存)
    存放提前暴露的 Bean(半成品 Bean,尚未完成属性注入和初始化)。
    Key: Bean 名称,Value: 提前暴露的 Bean 实例。
  3. Singleton Factories Cache(三级缓存)
    存放 Bean 的工厂对象(ObjectFactory),用于创建 Bean 的早期引用。
    Key: Bean 名称,Value: ObjectFactory

2. 解决循环依赖的流程

以下是一个典型的循环依赖场景:

  • BeanA 依赖于 BeanBBeanB 又依赖于 BeanA

解决步骤:

  1. 创建 BeanA
    • Spring 开始创建 BeanA,首先调用 BeanA 的构造函数,生成一个半成品对象(此时 BeanA 还未完成属性注入和初始化)。
    • 将 BeanA 的工厂对象(ObjectFactory)放入三级缓存中。
  2. 注入 BeanA 的依赖
    • Spring 发现 BeanA 依赖于 BeanB,于是开始创建 BeanB
  3. 创建 BeanB
    • Spring 调用 BeanB 的构造函数,生成一个半成品对象(此时 BeanB 还未完成属性注入和初始化)。
    • 将 BeanB 的工厂对象(ObjectFactory)放入三级缓存中。
  4. 注入 BeanB 的依赖
    • Spring 发现 BeanB 依赖于 BeanA,于是尝试从缓存中获取 BeanA
    • 三级缓存中获取 BeanA 的工厂对象,调用 getObject() 方法生成 BeanA 的早期引用(半成品)。
    • 将 BeanA 的早期引用放入二级缓存,并从三级缓存中移除 BeanA 的工厂对象。
  5. 完成 BeanB 的初始化
    • Spring 将 BeanA 的早期引用注入到 BeanB 中,完成 BeanB 的属性注入和初始化。
    • 将完全初始化的 BeanB 放入一级缓存
  6. 完成 BeanA 的初始化
    • Spring 将完全初始化的 BeanB 注入到 BeanA 中,完成 BeanA 的属性注入和初始化。
    • 将完全初始化的 BeanA 放入一级缓存

3. 代码示例

以下是一个典型的循环依赖示例:

@Component
public class BeanA {
    private BeanB beanB;

    @Autowired
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private BeanA beanA;

    @Autowired
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

在这个例子中:

  • BeanA 依赖于 BeanB
  • BeanB 依赖于 BeanA

Spring 通过三级缓存机制解决了这种循环依赖问题。


4. 注意事项

  1. 构造函数注入无法解决循环依赖
    如果使用构造函数注入,Spring 无法提前暴露 Bean 的早期引用,因此会抛出 BeanCurrentlyInCreationException 异常。
    @Component
    public class BeanA {
        private final BeanB beanB;
    
        @Autowired
        public BeanA(BeanB beanB) {
            this.beanB = beanB;
        }
    }
    
    @Component
    public class BeanB {
        private final BeanA beanA;
    
        @Autowired
        public BeanB(BeanA beanA) {
            this.beanA = beanA;
        }
    }
  2. Setter 注入或字段注入可以解决循环依赖
    Spring 可以在 Bean 实例化后再注入依赖,因此 Setter 注入或字段注入是解决循环依赖的推荐方式。
  3. 原型(Prototype)作用域的 Bean 不支持循环依赖
    Spring 只支持单例(Singleton)作用域的 Bean 解决循环依赖,原型作用域的 Bean 会直接抛出异常。

5. 总结

Spring 通过三级缓存机制解决了单例 Bean 的循环依赖问题:

  • 一级缓存存放完全初始化的 Bean。
  • 二级缓存存放提前暴露的 Bean(半成品)。
  • 三级缓存存放 Bean 的工厂对象,用于生成早期引用。

理解三级缓存机制和循环依赖的解决流程,对于深入掌握 Spring 框架非常重要。

THE END
点赞14 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容