互相引用 spring_听说你还不知道Spring是如何解决循环依赖问题的?

作者:Vt

前言

Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题。

其实笔者本人对这类框架源码题还是持一定的怀疑态度的。

如果笔者作为面试官,可能会问一些诸如“如果注入的属性为null,你会从哪几个方向去排查”这些场景题

那么既然写了这篇文章,闲话少说,发车看看Spring是如何解决的循环依赖,以及带大家看清循环依赖的本质是什么。

正文

通常来说,如果问Spring内部如何解决循环依赖,一定是单默认的单例Bean中,属性互相引用的场景。

比如几个Bean之间的互相引用:

016d94de68ec3340c7ffadc6d0c370c3.png

甚至自己“循环”依赖自己:

fe938366536800b562ef2c60c3c8d60d.png

先说明前提:原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。

if (isPrototypeCurrentlyInCreation(beanName)) {  throw new BeanCurrentlyInCreationException(beanName);}

原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A...

这就套娃了, 你猜是先StackOverflow还是OutOfMemory

Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException

117a240c91b81ff7e044030e22ffe9e4.png

基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。

那么默认单例的属性注入场景,Spring是如何支持循环依赖的?

Spring解决循环依赖

首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存

笔者翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。

在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:

  • singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。
  • singletonFactories 映射创建Bean的原始工厂
  • earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.

后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。

所以笔者前文对“三级缓存”这个词有些迷惑,可能是因为注释都是以Cache of开头吧。

为什么成为后两个Map为垫脚石,假设最终放在singletonObjects的Bean是你想要的一杯“凉白开”。

那么Spring准备了两个杯子,即singletonFactories和earlySingletonObjects来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects中。

闲话不说,都浓缩在图里。

aae44a9751865e7030f97f4b9bbe6148.gif

上面的是一张GIF,如果你没看到可能还没加载出来。三秒一帧,不是你电脑卡。

笔者画了17张图简化表述了Spring的主要步骤,GIF上方即是刚才提到的三级缓存,下方展示是主要的几个方法。

当然了,这个地步你肯定要结合Spring源码来看,要不肯定看不懂。

如果你只是想大概了解,或者面试,可以先记住笔者上文提到的“三级缓存”,以及下文即将要说的本质。

循环依赖的本质

上文了解完Spring如何处理循环依赖之后,让我们跳出“阅读源码”的思维,假设让你实现一个有以下特点的功能,你会怎么做?

  • 将指定的一些类实例为单例
  • 类中的字段也都实例为单例
  • 支持循环依赖

举个例子,假设有类A:

public class A {    private B b;}

类B:

public class B {    private A a;}

说白了让你模仿Spring假装AB是被@Component修饰, 并且类中的字段假装是@Autowired修饰的,处理完放到Map中。

其实非常简单,笔者写了一份粗糙的代码,可供参考:

    /**     * 放置创建好的bean Map     */    private static Map cacheMap = new HashMap<>(2);    public static void main(String[] args) {        // 假装扫描出来的对象        Class[] classes = {A.class, B.class};        // 假装项目初始化实例化所有bean        for (Class aClass : classes) {            getBean(aClass);        }        // check        System.out.println(getBean(B.class).getA() == getBean(A.class));        System.out.println(getBean(A.class).getB() == getBean(B.class));    }    @SneakyThrows    private static  T getBean(Class beanClass) {        // 本文用类名小写 简单代替bean的命名规则        String beanName = beanClass.getSimpleName().toLowerCase();        // 如果已经是一个bean,则直接返回        if (cacheMap.containsKey(beanName)) {            return (T) cacheMap.get(beanName);        }        // 将对象本身实例化        Object object = beanClass.getDeclaredConstructor().newInstance();        // 放入缓存        cacheMap.put(beanName, object);        // 把所有字段当成需要注入的bean,创建并注入到当前bean中        Field[] fields = object.getClass().getDeclaredFields();        for (Field field : fields) {            field.setAccessible(true);            // 获取需要注入字段的class            Class> fieldClass = field.getType();            String fieldBeanName = fieldClass.getSimpleName().toLowerCase();            // 如果需要注入的bean,已经在缓存Map中,那么把缓存Map中的值注入到该field即可            // 如果缓存没有 继续创建            field.set(object, cacheMap.containsKey(fieldBeanName)                    ? cacheMap.get(fieldBeanName) : getBean(fieldClass));        }        // 属性填充完成,返回        return (T) object;    }

这段代码的效果,其实就是处理了循环依赖,并且处理完成后,cacheMap中放的就是完整的“Bean”了

d805a130d75983869bb8d6fc4320dcf8.png

这就是“循环依赖”的本质,而不是“Spring如何解决循环依赖”。

之所以要举这个例子,是发现一小部分盆友陷入了“阅读源码的泥潭”,而忘记了问题的本质。

为了看源码而看源码,结果一直看不懂,却忘了本质是什么。

如果真看不懂,不如先写出基础版本,逆推Spring为什么要这么实现,可能效果会更好。

what?问题的本质居然是two sum!

看完笔者刚才的代码有没有似曾相识?没错,和two sum的解题是类似的。

不知道two sum是什么梗的,笔者和你介绍一下:

two sum是刷题网站leetcode序号为1的题,也就是大多人的算法入门的第一题。

常常被人调侃,有算法面的公司,被面试官钦定了,合的来。那就来一道two sum走走过场。

问题内容是:给定一个数组,给定一个数字。返回数组中可以相加得到指定数字的两个索引

比如:给定nums = [2, 7, 11, 15], target = 9 那么要返回 [0, 1],因为2 + 7 = 9

这道题的优解是,一次遍历+HashMap:

class Solution {    public int[] twoSum(int[] nums, int target) {        Map map = new HashMap<>();        for (int i = 0; i 

先去Map中找需要的数字,没有就将当前的数字保存在Map中,如果找到需要的数字,则一起返回。

和笔者上面的代码是不是一样?

先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。

结尾

如果你是上文笔者提到的“陷入阅读源码的泥潭”的读者,上文应该可以帮助到你。

可能还有盆友有疑问,为什么一道“two-sum”,Spring处理的如此复杂? 这个想想Spring支持多少功能就知道了,各种实例方式..各种注入方式..各种Bean的加载,校验..各种callback,aop处理等等..

Spring可不只有依赖注入,同样Java也不仅是Spring。如果我们陷入了某个“牛角尖”,不妨跳出来看看,可能会更佳清晰哦。

来源:掘金 链接:https://juejin.im/post/5e927e27f265da47c8012ed9

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/563088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打印多页时两边取消留白_办公必备技巧:Word打印技巧大全

打印是每一位办公族都要掌握的一项最基础的技能。平常在用WORD写作、对文章进行排版等&#xff0c;最终我们都是需要将其打印到纸张上。在打印中有着众多的操作技巧&#xff0c;如果你还不会&#xff0c;那今天易老师就来给你科普一下。本文目录第一部分&#xff1a;基础打印操…

中raise抛出异常_Python 异常处理知识点汇总,五分钟就能学会 !

异常处理在任何一门编程语言里都是值得关注的一个话题&#xff0c;良好的异常处理可以让你的程序更加健壮&#xff0c;清晰的错误信息更能帮助你快速修复问题。在Python中&#xff0c;和不分高级语言一样&#xff0c;使用了try/except/finally语句块来处理异常&#xff0c;如果…

python中print语法错误_Python 3.x中使用print函数出现语法错误(SyntaxError: invalid syntax)的原因...

在安装了最新版本的Python 3.x版本之后&#xff0c;去参考别人的代码(基于Python 2.x写的教程)&#xff0c;去利用print函数&#xff0c;打印输出内容时&#xff0c;结果却遇到print函数的语法错误&#xff1a;SyntaxError: invalid syntax这是因为Python 2.x升级到Python 3.x&…

easyexcel多个sheet导入_Java中Easypoi实现excel多sheet表导入导出功能

Easypoi简化了开发中对文档的导入导出实现&#xff0c;并不像poi那样都要写大段工具类来搞定文档的读写。第一步引入Easypoi依赖cn.afterturneasypoi-spring-boot-starter4.2.0Easypoi的注解使用说明(存留查看即可)第二步定义对应表格头数据对象实体类(注解的使用可以查阅上面的…

mysql 严格模式_MySQL 开启/关闭 严格模式(Strict Mode)

[广告&#xff1a;最高 2000 红包]阿里云服务器、主机等产品通用&#xff0c;可叠加官网常规优惠使用 | 限时领取查看 Mysql 是否开启严格模式&#xff1a;打开 MySQL 配置文件 my.cnf(windows为my.ini)。搜索 sql-mode 如果搜索不到就代表 非严格模式 。搜索到了就代表开启了严…

mysql 导入日期 0000_解决Excel导入MySQL日期为0000-00-00

最近在为客户做一个库存升级改造的项目&#xff0c;之前客户的数据管理全部是在Excel中操作&#xff0c;估计以前也是没有意识到数据量变大以后&#xff0c;工作会变得如此困难&#xff0c;基本上处于一个无法操作的程度了。于是我们将旧版本的Excel表格格式化以后&#xff0c;…

关于mysql优化_关于MySQL优化的几点总结

前言现如今&#xff0c;数据库的操作越来越成为整个应用的性能瓶颈了&#xff0c;这点对于Web应用尤其明显。所以&#xff0c;我整理了MySQL优化的几点建议&#xff0c;希望这些优化技巧对您有用&#xff0c;总结不到的&#xff0c;欢迎大家补充。SQL执行慢的原因网络速度慢&am…

ubuntu忘记mysql密码_ubuntu 忘记mysql 密码解决方法

一段时间没有用mysql数据库&#xff0c;今天突然需要使用&#xff0c;结果忘记密码&#xff0c;google了下找到的解决方法&#xff0c;就顺便记录下&#xff0c;下次碰到就不需要这么麻烦了1、输入命令 cat /etc/mysql/debian.cnf2、使用账号 debian-sys-maint 账号登录mysql密…

navicat mysql创建表_Navicat for MySQL如何创建数据表

Navicat for MySQL是针对MySQL数据库管理而研发的管理工具,创建数据表是其最基本操作,本教程将详解Navicat for MySQL创建数据表的方法。 步骤一:新建连接 运行Navicat数据库管理工具,连接本地数据库。点击左上角“文件”或者工具栏“连接”图标,创建自己的数据库连接。Na…

python mysql dbutils_python操作mysql数据库增删改查的dbutils实例

#encodingutf-8importMySQLdbimportgconf#主类classMysqlConnection(object):def __init__(self, host, port, user, passwd, db, charsetutf8):self.__host hostself.__port portself.__user userself.__passwd passwdself.__db dbself.__charset charsetself.__conn Noneself…

abp mysql .net core_ABP Asp.Net Core 集成 MySql 数据库

ASP.NET Boilerplate(简称ABP)是.Net平台下一个很流行的DDD框架&#xff0c;该框架已经为我们提供了大量的函数&#xff0c;非常方便与搭建企业应用。官方文档&#xff1a;http://www.aspnetboilerplate.com/Pages/DocumentsABPEFSQL Server是比较推荐的组合&#xff0c;由于使…

jboss mysql驱动目录_找不到mysql.jdbc.Driver – MySQL,JBoss

我无法将我的Web应用程序与MySQL 5.5.11后端部署到JBoss 5.我收到此错误&#xff1a;引起&#xff1a;java.lang.ClassNotFoundException&#xff1a;来自BaseClassLoader 262b2310的com.mysql.jdbc.Driver我在下面粘贴了我的堆栈跟踪.这就是我所做的……我将mysql-connector-…

mysql-5.1.73-8.el6_在centos中安装mysql详细步骤说明

Last login: Sun Dec 24 04:55:59 20171、安装依赖[rootnode001 ~]# yum install -y perlLoaded plugins: fastestmirrorLoading mirror speeds from cached hostfilebase | 3.7 kB 00:00extras | 3.4 kB 00:00updates | 3.4 kB 00:00Setting up Install ProcessPackage 4:perl…

mysql视图存储_Mysql 视图、存储过程以及权限控制

导读&#xff1a;该文章为视图、存储过程、用户权限练习&#xff1b;如果有不对的地方欢迎指出与补充&#xff1b;该基础练习基于MySQL5.0以上&#xff1b;语句格式&#xff1a;1. 视图格式&#xff1a;create view view_name[列名&#xff0c;列名.....] as select 子查询 wit…

21天学MySQL_SQL21天自学通.pdf

SQL21天自学通SQL 21 日自学通(V1.0) 翻译人 笨猪目录目录 1译者的话 14第一周概貌 16从这里开始 16第一天 SQL 简介 17SQL 简史 17数据库简史 17设计数据库的结构 21SQL 总览 23流行的SQL 开发工具 24SQL 在编程中的应用 27第二天 查询— — SELECT 语句的使用 30目标 30背景 …

lte盲重定向_LTE重选、切换、重定向的区别

【资料名称】&#xff1a;LTE重选、切换、重定向的区别【资料作者】&#xff1a;A【资料日期】&#xff1a;20150916【资料语言】&#xff1a;中文【资料格式】&#xff1a;DOC/DOCX【资料目录和简介】&#xff1a;这里主要简单阐述了LTE系统的小区重选、切换、重定向的区别小区…

冯乐乐 unity_Unity常用矩阵运算的推导补遗——切线空间

在上一篇文章中&#xff0c;我写了一些关于Unity中各个坐标空间及其转换矩阵是如何得到的&#xff0c;说实在的&#xff0c;我是那种“记忆需要依靠外部装置存储”类、如同《攻壳机动队》的电子脑一样的人&#xff0c;每次遇到问题了再去对着笔记慢慢翻找才是我的风格&#xff…

mysql 字段类型设计_Mysql字段类型设计相关问题!-阿里云开发者社区

Mysql是以文件存储在我们的系统的硬盘上面&#xff0c;那么(1)当我们读取写入的时候就会有磁盘IO的问题(2)当我们存储的数据是以页单位存储&#xff0c;而且每页的大小是16K&#xff0c;那么我们要尽可能的让我们的一页数据存放的更多。表结构宽度不要太大&#xff0c;也就是列…

mysql保存时乱码了_MySQL保存中文乱码的原因和解决办法

(3)MySQL的字符集设置。这个是重点了&#xff0c;一般都是在这里搞错而出现了mysql乱码。mysql编码设置可以分为三种设置&#xff1a;数据库的编码、表的编码、和字段的编码。a、数据库的编码&#xff1a;在sqlyog工具中操作把&#xff0c;右击数据库点击更改数据库&#xff0c…

mysql字符串语法_MySQL语法模板 函数:字符串

返回字符串的ASCII码ascii(str)返回字符串的二进制码bin(n)字符串的位数bit_length(str)字符串的字符数char(n,... [using charset])字符串的字符数character_length(str)字符串的字符数char_length(str)压缩函数compress(string_to_compress)进制转换conv(n,from_base,to_base…