Spring循环依赖详解

一、什么是循环依赖

在Spring框架中,循环依赖是指两个或更多的Bean相互间直接或间接地依赖对方的情况。这种依赖关系可能导致Bean的实例化过程中出现问题,但Spring通过其内部的处理机制能够解决某些类型的循环依赖。

简单的说就是A依赖B,B依赖C,C依赖A这样就构成了循环依赖。

二、循环依赖的3种情况

1. 构造器循环依赖

当Bean通过构造器参数注入其他Bean,并且存在循环依赖时,Spring无法解决这种依赖。这是因为Bean的实例化需要依赖于尚未创建完成的其他Bean,从而导致死锁。

复制代码
public class StudentA {

    private final StudentB studentB;

    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
}


public class StudentB {

    private final StudentA studentA ;

    public StudentB(StudentA studentA) {
        this.studentA = studentA;
    }
}
2. 原型(prototype)模式下的setter注入循环依赖

循环依赖双方 scope 都是 prototype 的话,也会循环依赖失败,代码如下:

复制代码
@Service
@Scope("prototype")
public class AService {
    @Autowired
    BService bService;
}

@Service
@Scope("prototype")
public class BService {
    @Autowired
    AService aService;
}

两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

scope 为 prototype 意思就是每次请求都会创建一个新的实例对象,不用缓存里的。那么 AService 需要 BService,所以就去现场创建 BService,结果 BService 又需要 AService,继续现场创建,AService 又需要 BService...,所以最终就陷入到死循环了。

报错信息:

3. 单例(singleton)模式下的setter注入循环依赖

以上两种的循环依赖Spring并没有解决,需要开发者进行相应的设计调整或使用其他的依赖注入策略。但是单例模式下的setter注入循环依赖,Spring能够解决。

Spring的Bean实例化过程中会使用一个三级缓存机制:

  • 一级缓存(singletonObjects):存储已经完全初始化的单例Bean。
  • 二级缓存(earlySingletonObjects):存储尚未完全初始化的Bean,即已经实例化但尚未完成属性填充的Bean。
  • 三级缓存(singletonFactories):存储Bean的工厂对象,该对象可以创建Bean的实例并将其放置在二级缓存中。

当Spring检测到循环依赖时,它不会等待所有依赖都初始化完成,而是将Bean的工厂对象提前暴露给其他正在创建的Bean。这样,即使Bean尚未完成初始化,其他Bean也能引用它,从而打破了死锁状态。

懒加载解决循环依赖:

在某些情况下,可以使用****@Lazy****注解来延迟Bean的初始化,从而避免循环依赖

public class BeanA {
@Autowired
@Lazy
private BeanB beanB;
}

public class BeanB {
@Autowired
private BeanA beanA;
}

三、三级缓存

Spring能够轻松的解决属性的循环依赖正式用到了三级缓存,在AbstractBeanFactory中有详细的注释。

  • 一级缓存:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用。
  • 二级缓存:earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean
  • 三级缓存:singletonFactories,三级缓存,存放实例化完成的Bean工厂。
1.getSingleton(beanName):源码如下:

从源码可以得知,doGetBean最初是查询缓存,一二三级缓存全部查询,如果三级缓存存在则将Bean早期引用存放在二级缓存中并移除三级缓存。(升级为二级缓存)

2. addSingletonFactory:源码如下

从源码得知,Bean在实例化完成之后会直接将未装配的Bean工厂存放在三级缓存中,并且移除二级缓存

3. addSingleton:源码如下:

Bean添加到一级缓存,移除二三级缓存。

相关推荐
程序猿乐锅43 分钟前
【黑马点评 | 第八篇】Redisson分布式锁
java·数据库·spring boot·redis·分布式·spring·缓存
福兮说44 分钟前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
m0_587383001 小时前
广州24小时自助健身房解决方案实战指南与系统部署要点
java·spring·小程序·架构·需求分析
需要8262 小时前
分布式事务:Seata AT/TCC/SAGA 的取舍与踩坑
java·spring boot·分布式·spring·spring cloud
m0_587383003 小时前
深圳24小时自助健身房解决方案实战:从系统架构到部署指南
java·人工智能·spring boot·spring·系统架构·需求分析
今年下半年3 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
可乐鸡翅yeah_6 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
kyriewen6 小时前
我实测了前端日期的 5 个坑:差 8 小时只是开始
前端·javascript·程序员
anew___6 小时前
《从零手写操作系统 (11):文件系统初探——initrd与VFS抽象层》
java·javascript·算法
今年下半年6 小时前
【VUE3】移动端:使用腾讯地图显示区域统计与放大显示资产点位
javascript·前端框架·vue·地图