Java开发-面试题-0024-什么是 Spring 的循环依赖,Spring是怎么解决的,三级缓存的作用有哪些

Java开发-面试题-0024-什么是 Spring 的循环依赖,Spring是怎么解决的,三级缓存的作用有哪些


更多内容欢迎关注我(持续更新中,欢迎Star✨)

Github:CodeZeng1998/Java-Developer-Work-Note

技术公众号:CodeZeng1998(纯纯技术文)

生活公众号:好锅(Life is more than code)

其他平台:CodeZeng1998好锅

1.什么是 Spring 的循环依赖

@Component
public class A {
    @Autowired
    private B b; 
}

@Component
public class B {
    @Autowired
    private A a; 
}

循环依赖:

  • 在创建A对象的同时需要使用的B对象,在创建B对象的同时需要使用到A对象
  • A需要B,B需要C,C需要A
  • A需要A

在Spring中,Bean的创建过程主要分为三个步骤:

  1. 实例化(Instantiation):创建Bean的实例。
  2. 依赖注入(Dependency Injection):为Bean的属性注入依赖。
  3. 初始化(Initialization):调用@PostConstruct等初始化方法。

如果没有三级缓存,Spring容器在实例化A时,会发现A依赖B,于是先去实例化B。但在实例化B的过程中,又会发现B依赖A,于是再次去实例化A,这时就会进入一个死循环。

2.Spring是怎么解决的循环依赖的

三级缓存解决循环依赖:

Spring 解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	// 一级缓存
	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 三级缓存
	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // 二级缓存
	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    ...
    ...
    ...
}
缓存名称源码名称作用
一级缓存singletonObjects单例池,缓存已经经历了完整的生命周期,已经初始化完成的 bean 对象,限制bean在beanFactory 中只存一份,即实现singleton scope
二级缓存earlySingletonObjects缓存早期的 bean 对象(生命周期还没走完)
三级缓存singletonFactories缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象

Spring 中的 bean 的生命周期

构造函数

-> 依赖注入

-> Aware 接口

-> BeanPostProcessor#before

-> 初始化方法

-> BeanPostProcessor#after

-> 使用 bean

-> 销毁 bean

Spring 的三级缓存机制是其解决循环依赖的原理:

1.三级缓存的结构

Spring中用于管理单例Bean的缓存有三级:

  • 一级缓存(singletonObjects):这是一个Map<String, Object>,存储已经完全初始化好的单例Bean,供外界直接使用。
  • 二级缓存(earlySingletonObjects):这是一个Map<String, Object>,存储已经实例化但尚未完成属性注入的单例Bean。主要用于解决Bean的代理问题。
  • 三级缓存(singletonFactories):这是一个Map<String, ObjectFactory<?>>,存储可以生成早期单例Bean的工厂对象。工厂用于生成还未进行依赖注入的Bean实例,并允许这些实例提前暴露。
  1. 循环依赖的场景分析

假设我们有两个互相依赖的类AB

@Component
public class A {
    @Autowired
    private B b; 
}

@Component
public class B {
    @Autowired
    private A a; 
}

3.当Spring容器启动时,会按照以下步骤创建和管理这些Bean

三级缓存的具体执行流程:

  • 第一步:创建A实例

    • Spring容器检测到需要创建Bean A,并开始实例化A

    • A的构造方法执行,Bean的实例被创建出来,但此时并未注入依赖(即B还未注入)。

    • Spring会将这个实例(实际上是一个工厂)放入三级缓存(singletonFactories)中,以便在后续的依赖注入过程中能够被其他Bean引用。

  • 第二步:创建B实例

    • A的创建未完成,因为A依赖于B,Spring开始创建B

    • 同样地,B的构造方法执行,实例化B,但此时B还未注入其依赖(即A)。

    • Spring发现B依赖于A,于是会去三级缓存(singletonFactories)中查找A的工厂对象。

    • Spring通过三级缓存中的ObjectFactory获取A的早期实例,并将其放入二级缓存(earlySingletonObjects)中。这一阶段,A并未完全初始化,但可以被B使用。

    • 然后Spring将A注入到B中,B的依赖注入完成。

  • 第三步:完成A的创建

    • 回到A的创建流程,B已经被创建且注入完成,可以安全地注入到A中。

    • Spring完成A的依赖注入和初始化,并将A从二级缓存移动到一级缓存(singletonObjects)中。

  • 第四步:完成B的创建

    • 最后,Spring完成B的初始化并将其放入一级缓存。
  1. 循环依赖的解决

通过三级缓存机制,Spring可以在创建过程中将未完全初始化的Bean提前暴露,并允许依赖方引用它。具体来说:

  • 三级缓存(singletonFactories 提供了一个回调机制,可以让其他Bean在需要时获取一个尚未完全初始化的Bean。
  • 二级缓存(earlySingletonObjects 用于存储从三级缓存中获取的早期实例,使得已经在依赖关系中使用的实例不会再次通过工厂生成,避免重复。
  • 一级缓存(singletonObjects 最终存储完全初始化的Bean,供全局使用。

这种机制确保了即使在存在循环依赖的情况下,Spring也能够顺利完成Bean的创建和依赖注入。

Spring中的循环引用

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
  • 循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
  • 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
  • 二级缓存:缓存早期的bean对象(生命周期还没走完)
  • 三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

3.三级缓存解决不了循环依赖的情况

三级缓存能解决大部分的循环依赖问题,但是有些是解决不了的,需要我们手动解决。

构造方法出现了循环依赖怎么解决?

A依赖于B,B依赖于A,注入的方式是构造函数

@Component
public class A {
    private B b; 
    public A(B b){
        System.out.println("A的构造方法执行了.");
            this.b = b;
    }
}

@Component
public class B {
    private A a; 
    public B(A a){
        System.out.println("B的构造方法执行了.");
            this.a = a;
    }
}

报错信息:Is there an unresolvable circular reference?

原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入

解决方案:使用**@Lazy**进行懒加载,什么时候需要对象再进行bean对象的创建

public A(@Lazy B b){
    System.out.printin("A的构造方法执行了.");
    this.b=b;
}

4.Spring 三级缓存的作用

  • 解决循环依赖(上文所述)
  • 提高性能:已经完成创建的 Bean 对象存储在一级缓存当中,后续需要使用时直接获取即可,避免重复创建,提供了系统性能和响应速度
  • 实现懒加载二级缓存可以实现 Bean的懒加载,当需要使用到 Bean 时才进行对应的初始化操作,而不是启动时直接创建对象。
  • 保证单例:Spring 中的对象默认是单例的,完成创建的 Bean 对象全部存储在一级缓存当中。

以上就是本文相关的所有内容了,如果发现有误欢迎评论指正,更多内容欢迎各位关注。

在这里插入图片描述

上图是由 Pic 生成的

关键词:Cute turtle


更多内容欢迎关注我(持续更新中,欢迎Star✨)

Github:CodeZeng1998/Java-Developer-Work-Note

技术公众号:CodeZeng1998(纯纯技术文)

生活公众号:好锅(Life is more than code)

其他平台:CodeZeng1998好锅

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值