面试题-Spring循环依赖问题

什么是Spring的循环依赖

Spring 框架中的循环依赖指的是在 Spring 容器中,两个或多个 bean 之间存在相互依赖的情况。这种依赖关系会导致循环引用,使得 Spring 需要在创建这些 bean 时采取特殊的处理措施。

例如,假设有两个 bean A 和 B,其中 A 依赖于 B,而 B 又依赖于 A。如果不加以处理,这种情况会导致创建 bean 的过程中出现问题,因为每个 bean 的创建都依赖于另一个尚未完全创建的 bean。

public class B {
    private A a;
}
public class A {
    private B b;
}

Spring是如何解决的(三级缓存)

构造器注入的循环依赖

对于构造器注入,Spring 无法自动解决循环依赖,因为在构造函数调用时,依赖的 bean 尚未完全初始化。因此,构造器注入的循环依赖一般会导致 BeanCurrentlyInCreationException 异常。为了解决这个问题,可以选择使用 setter 注入或构造函数注入来避免这种情况。

Setter 注入或属性注入的循环依赖:

使用注解方式(例如 @Autowired)时,Spring 默认使用的是 setter 注入方式(在字段上也可以使用 @Autowired,它实际上是 setter 注入的一种简化)

对于 setter 注入,Spring 能够通过三级缓存机制来解决循环依赖问题。

三级缓存机制

Spring 在创建 bean 的过程中维护了三个主要的缓存:

  1. 一级缓存

    • 功能:存储完全初始化的 bean 实例。这个缓存保证了一个 bean 在 Spring 容器中是唯一的,并且在容器中被引用时是完全初始化的。
    • 源码:在 DefaultSingletonBeanRegistry 类中,一级缓存是通过 singletonObjects 维护的。
      private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
      
  2. 二级缓存

    • 功能:存储正在创建中的 bean 实例的引用。当一个 bean 被创建但尚未完全初始化时,它会被放入这个缓存中,以供其他 bean 访问(解决循环依赖)。
    • 源码:在 DefaultSingletonBeanRegistry 类中,二级缓存是通过 earlySingletonObjects 维护的。
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
  3. 三级缓存

    • 功能:存储 bean 的工厂方法引用。这个缓存用于处理正在创建中的 bean 的循环依赖问题。它确保在 bean 实例创建期间,其他 bean 可以获得一个工厂方法来完成 bean 的创建。
    • 源码:在 DefaultSingletonBeanRegistry 类中,三级缓存是通过 singletonFactories 维护的。
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    

解决循环依赖的具体流程:

  1. 创建Bean的过程:
    • 当Spring容器需要创建一个Bean时,会先检查singletonObjects中是否已有该Bean的实例。
    • 如果没有,检查earlySingletonObjects和singletonFactories中是否存在正在创建中的Bean的代理对象。
  2. 三级缓存的使用:
    • 步骤1: Spring先将Bean的Factory放入singletonFactories缓存中。
    • 步骤2: 如果Bean的初始化过程中遇到循环依赖,Spring会从earlySingletonObjects中查找已经创建中的Bean的代理对象。
    • 步骤3: 一旦Bean的创建完成,Spring会将该Bean从earlySingletonObjects中移除,并放入singletonObjects缓存中,以便其他Bean可以使用这个完全初始化好的实例。

三级缓存解决循环依赖问题的关键在于:
实例化和初始化分开操作,在中间过程中给其他对象赋值的时候,并不是一个完整对象,而是把半成品对象赋值给了其他对象。

相关源码

  • 主要看DefaultSingletonBeanRegistry.java
    在这里插入图片描述在这里插入图片描述
    辅助理解流程图:
    以上A,B两个类互相依赖的例子,描述实例化,初始化,一级缓存,二级缓存,三级缓存,在初始化过程中的大概流程。
    在这里插入图片描述

只用一级缓存能解决问题吗

不能!
在整个处理过程中,缓存中放的是半成品和成品对象,如果只有一级缓存,那么成品和半成品都会放到一级缓存中,有可能在获取过程中获取到半成品对象,此时半成品对象是无法使用的,不能直接进行相关的处理,因此要把半成品和成品的存放空间分割开来。

只用二级缓存能解决问题吗

如果我能保证所有的bean对象都不去调用getEarlyBeanReference此方式就可以。
在这里插入图片描述
使用三级缓存的本质在于缺乏Bean Factory的代理支持: 二级缓存仅存储正在创建中的Bean实例,但在某些情况下,Bean的创建可能涉及到代理对象(例如,Spring AOP创建的代理对象)。单独使用二级缓存无法处理这些代理对象的需求。

为什么使用了三级缓存就可以解决这个问题?

当一个对象需要被代理的时候,在整个创建过程中是包含两个对象吧。一个是普通对象,一个代理生成的对象,bean默认都是单例,那么我在整个生命周期的处理环节中,一个beanname能对应两个对象吗?不能,既然不能的话,保证我在使用的时候加一层判断,判断一下是否需要进行代理的处理。
因为不知道什么时候回调用,所以通过一个匿名内部类的方式,在使用的时候直接对普通对象进行覆盖操作,保证全局唯一!!!

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值