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

1.什么是循环依赖

循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环。

2.循环依赖的两种情况及其解决方案

2.1 构造器的循环依赖

这种依赖 spring 本身是处理不了的

在这种情况下 Spring 将在加载上下文时引发 BeanCurrentlyInCreationException 异常,因为 Spring 无法决定应该先创建哪个 bean,因为它们彼此相互依赖。

当我们使用构造方法进行注入时,才会遇到这种情况,因为它在上下文加载时就被要求注入。setter 和 field 方法注入则不会发生循环依赖,因为它们不会在创建 Bean 时就注入依赖,而是在被需要时才注入。

解决方案:

  • 采用 setter 和 field 方法注入
  • 使用懒加载 @Lazy
  • 使用 @PostConstruct
  • 实现 ApplicationContextAware 和 InitializingBean 接口

2.2 单例模式下的 setter 循环依赖

Spring 解决循环依赖的机制是根据 Spring 框架内定义的三级缓存来实现的,三级缓存解决了 Bean 之间的循环依赖问题。

从源码中来看看 Spring 中 Bean 工厂是怎么获取 Bean 的(AbstractBeanFactory中):

一级一级向下寻找,可以找到 DefaultSingletonBeanRegistry 类中的三级缓存,其实就是三个 Map 集合类:

  • 第一级缓存【singletonObjects】:里面存放的是实例化好和完成初始化的单例对象
  • 第二级缓存【earlySingletonObjects】:里面存放的是提前曝光的单例对象,这个 Bean 实例化了,但是还没有初始化
  • 第三级缓存【singletonFactories】:里面存放的是要被实例化的对象的对象工厂。

所以当一个 Bean 调用构造函数进行实例化后,即使这时候属性还未填充,依然可以通过三级缓存向外暴露依赖的引用值(所以循环依赖问题的解决也是基于 Java 的引用传递),这也说明了另外一点,基于构造器的注入,如果有循环依赖,Spring是不能够解决的。

还有一点,Spring 默认的 Bean Scope 是单例的,而三级缓存中都包含 singleton,可见是对于单例 Bean 之间的循环依赖的解决。

其实二级缓存也是可以解决循环依赖的。为什么 Spring 不选择二级缓存?

如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而 Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存。但是因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。

你可能感兴趣的:(spring,java,后端)