首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >不是说Spring解决了循环依赖问题么-为什么项目还报了循环依赖的异常

不是说Spring解决了循环依赖问题么-为什么项目还报了循环依赖的异常

作者头像
Lvshen
发布于 2022-05-05 10:49:39
发布于 2022-05-05 10:49:39
1.3K0
举报

什么?SpringBoot项目启动报循环依赖异常

今天,我启动项目项目报循环依赖异常了,为了说明我这里简化了。

我的代码是这样(模拟)的

代码语言:javascript
AI代码解释
复制
@Component
public class TestA {
    @Autowired
    private TestB testB;

    @Async("taskExecutor")
    public TestB getTestB(){
        return testB;
    }
}
代码语言:javascript
AI代码解释
复制
@Component
public class TestB {
    @Autowired
    private TestA testA;

    public void testTrans() {
        testA.getTestA();
        System.out.println("testTrans线程名称:" + Thread.currentThread().getName());
    }
}

这里是TestA调用了TestB,TestB里面又调用了TestA。是一个典型的循环依赖场景,但是我们知道Spring对于循环依赖问题是做了处理的。但是这里为什么会报错?

循环依赖关联

为此我们来分析下Spring是如何解决循环依赖问题的。

循环依赖出现场景

我们来看看哪些情况属于循环依赖。

对象M的创建依赖S的创建,并且S的创建又依赖M的创建。

循环依赖定义1

对象M的创建依赖S的创建,S的创建依赖O的创建,O的创建依赖M的创建。这样也形成了一个闭环。

循环依赖定义2

还有自己依赖自己的。

循环依赖定义3

Bean实例化流程

我们来看看Spring容器如何获取bean流程的。

首先进入getBean()方法,里面调用了doGetBean()方法。

doGetBean()方法中,调用了getSingleton()方法,用于获取sharedInstance实例。

getSingleton()方法如下:

这个方法主要是从缓存中获取bean的单例,这里会判断,如果当前待获取的单列存在循环依赖,就会从其他缓存里面获取。我们先来看看bean实例主要从哪几个缓存里面获取。

  • singletonObjects
  • earlySingletonObjects
  • singletonFactories

缓存的数据结构为map:

那么这几个缓存是做什么用的呢?

首先我们要了解bean初始化经历的步骤

bean初始化

singletonObjects:完全初始化的对象都存入次map中

earlySingletonObjects:早期实例化对象,就是这个对象在内存中已经开辟了空间,但是并未填充里面的属性。这个缓存就是二级缓存,用于解决循环依赖。

singletonFactories:在属性填充之后,初始化之前。如果允许提前曝光,会将实例化的bean添加到此缓存 中,这里就是我们说的三级缓存。

那么,是如何从缓存里面获取实例的?

  • 先从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用;
  • 如果发现取出的实例为null或者正在创建中,就去二级缓存中获取;
  • 如果二级缓存中没有,从三级缓存中获取;
  • 如果三级缓存中有,将其移动到二级缓存中;
  • 如果三级缓存没有,直接返回null。
代码语言:javascript
AI代码解释
复制
Object sharedInstance = getSingleton(beanName);

当三级缓存返回null,也就是sharedInstance值为null。这时会创建bean。

创建bean的方法如下:

代码语言:javascript
AI代码解释
复制
createBean(beanName, mbd, args);

createBean()方法调用了如下方法

代码语言:javascript
AI代码解释
复制
Object beanInstance = doCreateBean(beanName, mbdToUse, args);

里面会执行

代码语言:javascript
AI代码解释
复制
instanceWrapper = createBeanInstance(beanName, mbd, args);

接下来调用addSingletonFactory,将bean添加到singletonFactories 缓存(三级缓存)。

代码语言:javascript
AI代码解释
复制
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

接下来再执行populateBean()

代码语言:javascript
AI代码解释
复制
populateBean(beanName, mbd, instanceWrapper);

populateBean()主要是向早期bean中填充属性,如果发现bean A在填充属性时需要依赖bean B,会创建B对象。这时发现B在填充属性值时有需要依赖A,就会从三级缓存中获取还未填充属性值的bean A。这时就可以把 A 对象的原始引用注入 B 对象(并将其移动到二级缓存)来解决循环依赖问题。这时候 getObject() 方法就算执行结束了,返回完全实例化的 bean。

最后将完全实例化好的bean对象存入一级缓存singletonObjects中。

我们来看看获取bean的流程图:

循环依赖解决图

从上面可知,出现循序环依赖时,会从三级缓存中获取早期曝光的bean,然后将其移动到二级缓存。

只用二级缓存可以解决循环依赖问题么

这时候你是不是有疑问,为什么需要三级缓存?二级缓存能否解决循环依赖问题呢?

我们来看这段代码

如果获取的对象是一个代理对象,这里的singletonFactory.getObject()会生成新的代理对象,为了保证只有一个代理对象,需要选用缓存来缓存代理对象。

那么回到文章开头的问题,标注了@Async注解的方法的bean,为什么Spring没有解决循环依赖问题。

实际上@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来。@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization创建的代理。

那么你可能有疑问@Transactional注解的实现不是和@Async样么,那会出现循环依赖问题么。答案是不会出现,因为@Transactional使用的是自动代理创建器AbstractAutoProxyCreator,它实现了getEarlyBeanReference()方法从而很好的对循环依赖提供了支持。

除了@Async@Validated也会出现同样问题哦。

问题

大家可以思考下,哪些情况会出现循环依赖问题?并如何采取相应的解除办法。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Lvshen的技术小屋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
MySQL 8.0 数据字典有哪些变化?
墨墨导读:MySQL8.0 数据字典(Data Dictionary)也在进化中。MyISAM系统表全部换成InnoDB表 ,支持原子DDL。复杂度增加了。考虑过是否跟业务数据库有资源抢夺的现象,这些都是实际使用中需要观察关注的问题。
数据和云
2020/06/17
2.4K0
MySQL 8.0新特性: 数据字典
目前MySQL 8.0最新版本为8.0.23版本,针对8.0的新特性,从春节前开始做了一些相关学习和测试,后续会不阶段的分享一些8.0的新特性,供大家一起参考和学习;
SEian.G
2021/03/03
2.5K0
MySQL8功能详解——数据字典
MySQL与其它的数据库一样,需要一个储存元数据的地方。在MySQL8之前,它们以各种文件的形式保存在不同的地方,例如 .FRM , .TRG ,.TRN等等。随着时间的推移,这些文件逐渐成为了各种环境中的瓶颈。MySQL8推出了支持事务的数据字典。
MySQLSE
2020/09/28
1.2K0
MySQL8功能详解——数据字典
你真的了解MySQL 8.0 数据字典吗?
作者:叶盛,腾讯云数据库TDSQL开发工程师,从事数据库内核开发工作。 在MySQL中,数据字典信息内容包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。可是包含这些元数据的数据字典不仅仅存在于数据库系统表中(information_schema,mysql,sys),还存在于server层和InnoDB存储引擎中的部分文件里,比如每个表都有一个对应的.frm文件来保存表结构的信息,.opt文件用来用来记录每个库的字符等信息,.TRN和.TRG文件用来存放触发器的
腾讯云数据库 TencentDB
2020/09/04
1.4K0
深入解析MySQL 8:事务数据字典的变革
在MySQL 8之前的版本中,元数据分散地存储在多个地方,包括元数据文件、非事务性表和特定于存储引擎的数据字典中。这种分散的存储方式不仅增加了管理的复杂性,还可能导致数据的不一致性。为了解决这些问题,MySQL 8引入了事务数据字典,将元数据集中存储在具有事务功能的InnoDB表中,从而提供了一致性和可靠性的保证。
公众号:码到三十五
2024/03/19
3180
MySQL 8.0新特性 — 事务性数据字典与原子DDL
事务性数据字典与原子DDL,是MySQL 8.0推出的两个非常重要的新特性,之所以将这两个新特性放在一起,是因为两者密切相关,事务性数据字典是前提,原子DDL是一个重要应用场景。
brightdeng@DBA
2020/08/17
2K0
MySQL 8.0新特性 — 事务性数据字典与原子DDL
详解MySQL-8.0数据字典
提示:公众号展示代码会自动折行,建议横屏阅读 ---- 1. 引言 ---- 数据字典(Data Dictionary)中存储了诸多数据库的元数据信息如图1所示,包括基本Database, table, index, column, function, trigger, procedure,privilege等;以及与存储引擎相关的元数据,如InnoDB的tablespace, table_id, index_id等。MySQL-8.0在数据字典上进行了诸多优化,本文将对其进行逐一介绍。 图1 2.
腾讯数据库技术
2019/05/16
7K0
详解MySQL-8.0数据字典
MySQL-8.0 | 数据字典最强解读
数据字典(Data Dictionary)中存储了诸多数据库的元数据信息如图1所示,包括基本Database, table, index, column, function, trigger, procedure,privilege等;以及与存储引擎相关的元数据,如InnoDB的tablespace, table_id, index_id等。MySQL-8.0在数据字典上进行了诸多优化,本文将对其进行逐一介绍。
数据和云
2019/05/13
4.3K0
2020-01-26:mysql8.0做了什么改进?
在MySQL5.7中,所有的临时表都被创建在一个叫“ibtmp1”的表空间中。另外,临时表的元数据也将存储在内存中(不再存储在frm文件中)。
福大大架构师每日一题
2021/01/26
1.1K0
MySQL 8.0 数据字典表
MySQL 8.0 对数据字典进行了重构,用户表、数据字典表、MySQL 其它系统表的元数据都统一保存到 mysql 库的数据字典表中了。
csch
2022/12/20
2.1K0
MySQL 8.0 数据字典表
MySQL 8.0数据字典有什么变化
从MySQL 8.0开始,采用独立表空间模式的每个InnoDB表只有一个 .ibd 表空间文件,而不再有 .frm 文件了。为了实现DDL的原子性,InnoDB直接把元数据存储在表空间文件中,需要的话,可是使用 ibd2sdi 工具从中读取,例如:
老叶茶馆
2022/12/02
1.1K0
MySQL8.0原子DDL语法
原子DDL语句将数据字典更新、存储引擎操作和与DDL操作相关联的二进制日志写入合并到单个原子操作中。该操作要么提交,对数据字典、存储引擎和二进制日志保留适用的更改,要么回滚。
AsiaYe
2020/12/31
7220
我们为何对MySQL 8.0的到来感到兴奋!
一大早收到一封oracle官方发来的邮件,邀请我参加mysql改版的网路研讨会。作为一个后端开发者,想必对mysql是非常是熟悉了。下面来聊一聊mysql8.0的新特性。 临时表的改进 在MySQL5.7中,所有的临时表都被创建在一个叫“ibtmp1”的表空间中。另外,临时表的元数据也将存储在内存中(不再存储在frm文件中)。 在MySQL8.0中,使用临时表存储引擎作为临时表(为优化JOIN、UNION等操作而创建的)存储的默认引擎,从而替换掉了原有的内存存储引擎。 新的引擎使得VARCHAR和VARBI
三哥
2018/06/15
1.1K0
深度解读 MySQL 8.0 数据字典重构:源码解析与实践
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
喵手
2024/10/29
3200
深度解读 MySQL 8.0 数据字典重构:源码解析与实践
MySQL 8.0 之原子DDL
听到原子这个关键字大家是不是联想到事务的ACID的原子性?两者相似,事务/语句执行要么全部成功,要么全部失败。MySQL 8.0 之前的版本 DDL 是非原子性的,对于多条sql构成的ddl语句比如 rename table t1 to t1_bak,t2 to t2_bak; 执行过程中如果遇到系统异常crash,有可能出现表t1被rename,但是t2没有被rename的情况。出现该情况的原因就是MySQL不支持原子的DDL。
用户1278550
2020/08/21
1.2K0
MySQL8.0​ 字典表增强的意义
MySQL中数据字典是数据库重要的组成部分之一,INFORMATION_SCHEMA首次引入于MySQL 5.0,作为一种从正在运行的MySQL服务器检索元数据的标准兼容方式。用于存储数据元数据、统计信息、以及有关MySQL server的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等)。
MySQL轻松学
2020/06/23
9530
MySQL 8.0新特性之原子DDL
MySQL 8.0 开始支持原⼦ DDL(atomic DDL),数据字典的更新,存储引擎操作,写⼆进制日志结合成了一个事务。在没有原⼦DDL之前,DROP TABLE test1,test2;如遇到server crash,可能会有test1被drop了,test2没有被drop掉。下面来看下在MySQL 8.0之前和MySQL 8.0 数据字典的区别。
星哥玩云
2022/08/17
5370
MySQL 8.0新特性之原子DDL
mysql8.0原子ddl特性
MySQL8.0支持原子DDL。原子DDL将DDL操作相关联的数据字典更新、存储引擎操作和二进制日志写入合并到单个原子事务中。
卖菜小弟
2020/03/05
1.2K0
MySQL8.0几个有用的新特性
今晚有点事情耽搁了,昨天下午测试了几个MySQL8.0的新特性,写在这里,希望对大家有所帮助。
AsiaYe
2020/05/18
2.7K0
MySQL 8.0.0 Changes 版本变更事项(2016-09-12, 开发里程碑)(施工现场)
原文链接: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-0.html
Fireflywang
2022/08/01
4800
相关推荐
MySQL 8.0 数据字典有哪些变化?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
首页
学习
活动
专区
圈层
工具
MCP广场
首页
学习
活动
专区
圈层
工具
MCP广场