互相引用 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;如果…

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…

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…

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…

python中convert函数用法_Python Pandas DataFrame.tz_convert用法及代码示例

Pandas DataFrame是带有标签轴(行和列)的二维大小可变的&#xff0c;可能是异构的表格数据结构。算术运算在行和列标签上对齐。可以将其视为Series对象的dict-like容器。这是 Pandas 的主要数据结构。Pandas DataFrame.tz_convert()用于将tz-aware轴转换为目标时区。用法&#…

python偶数个数_python基础

标识符命名规则开发中&#xff0c;通常约定俗称遵守如下规则&#xff1a;删除变量和垃圾回收机制可以通过del语句删除不在使用的变量a 123 del a #删除了栈&#xff0c;没有删除堆链式赋值xy123 相当于 x123;y123系列解包赋值系列数据赋值给对应相同…

keyshot怎么让物体发光_户外发光字的防水措施怎么做,不亮了怎么修。

户外发光字的防水措施与维修发光字大多是安装在户外的&#xff0c;因此会受到热晒、雨淋、寒冻等因素的影响&#xff0c;这些因素都会加速LED发光字的老化&#xff0c;从而使LED发光字进水老化。那么&#xff0c;该怎么防止这种情况呢?一、在每个广告字的笔画的最下方&#xf…

mysql删除员工_数据库删除职工信息

《数据库技术原理与应用教程第2版》——1.4数据管理本节书摘来自华章出版社《数据库技术原理与应用教程第2版》一书中的第1章&#xff0c;第1.4节&#xff0c;作者 徐洁磐 操凤萍  &#xff0c;更多章节内容可以访问云栖社区“华章计算机”公众号查看。1.4数据管理人类社会有着…

手撕包菜 mysql_手撕包菜搭建

概述最近做了两件事&#xff0c;一件事就是买了块1t硬盘&#xff0c;第二件事就是买了个百度云会员&#xff0c;无奈找不到资源下载&#xff0c;那就没办法了&#xff0c;搭建一个磁力链接搜索引擎来爬去链接&#xff0c;然后去找资源。说道磁力链接搜索引擎&#xff0c;最好的…

word流程图怎么使箭头对齐_word 流程图 怎么把箭头对整齐啊?

word里画流程图把箭bai头画直du,可通过shift键实现画出zhi垂直或水平的直箭头。方法步dao骤如下:回1、打开需要答操作的WORD文档,选中需要画出箭头的流程图框图,点解绘图工具中的“排列对齐”,然后选中“左右居中”对齐。2、在插入形状中找到并点击箭头,使用箭头形状工具…

java商品管理系统_【Java Web】简易商品信息管理系统——首个Web项目

正文之前在学习了一段时间的Java Web的内容之后&#xff0c;当然需要有个项目来练练手&#xff0c;我相信大多数人的首选项目都是信息管理系统吧&#xff0c;所以我选择了商品信息管理系统目前项目源码已全部上传至GitHub&#xff0c;欢迎大家来fork —— 商品信息管理系统正文…