Spring源码解析-Spring 循环依赖

news2025/7/18 0:43:31

Spring源码解析简图:

Spring 如何解决循环依赖,⽹上的资料很多,但是感觉写得好的极少,特别是源码解读⽅⾯,我就⾃⼰单独出⼀ 篇,这篇⽂章绝对肝!

文章目录:

一. 基础知识

1.1 什么是循环依赖 ?

⼀个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成⼀个环形调⽤,有下⾯ 3 种⽅式。

我们看⼀个简单的 Demo,对标“情况 2”。

@Service
public class Model1 {
    @Autowired
    private Model2 model2;
    public void test1() {
    }
}
@Service
public class Model2 {
    @Autowired
    private Model1 model1;
    public void test2() {
    }
}
复制代码

这是⼀个经典的循环依赖,它能正常运⾏,后⾯我们会通过源码的⻆度,解读整体的执⾏流程。

1.2 三级缓存

解读源码流程之前,spring 内部的三级缓存逻辑必须了解,要不然后⾯看代码会蒙圈。

  • 第⼀级缓存:singletonObjects,⽤于保存实例化、注⼊、初始化完成的 bean 实例;
  • 第⼆级缓存:earlySingletonObjects,⽤于保存实例化完成的 bean 实例;
  • 第三级缓存:singletonFactories,⽤于保存 bean 创建⼯⼚,以便后⾯有机会创建代理对象。

这是最核⼼,我们直接上源码:

执⾏逻辑:

  • 先从“第⼀级缓存”找对象,有就返回,没有就找“⼆级缓存”;
  • 找“⼆级缓存”,有就返回,没有就找“三级缓存”;
  • 找“三级缓存”,找到了,就获取对象,放到“⼆级缓存”,从“三级缓存”移除。

1.3 原理执⾏流程

我把“情况 2”执⾏的流程分解为下⾯ 3 步,是不是和“套娃”很像 ?

整个执⾏逻辑如下:

    1. 在第⼀层中,先去获取 A 的 Bean,发现没有就准备去创建⼀个,然后将 A 的代理⼯⼚放⼊“三级缓存”(这个 A 其实是⼀个半成品,还没有对⾥⾯的属性进⾏注⼊),但是 A 依赖 B 的创建,就必须先去创建 B;
    1. 在第⼆层中,准备创建 B,发现 B ⼜依赖 A,需要先去创建 A;
    1. 在第三层中,去创建 A,因为第⼀层已经创建了 A 的代理⼯⼚,直接从“三级缓存”中拿到 A 的代理⼯⼚,获 取 A 的代理对象,放⼊“⼆级缓存”,并清除“三级缓存”;
    1. 回到第⼆层,现在有了 A 的代理对象,对 A 的依赖完美解决(这⾥的 A 仍然是个半成品),B 初始化成功;
    1. 回到第⼀层,现在 B 初始化成功,完成 A 对象的属性注⼊,然后再填充 A 的其它属性,以及 A 的其它步骤 (包括 AOP),完成对 A 完整的初始化功能(这⾥的 A 才是完整的 Bean)。
    1. 将 A 放⼊“⼀级缓存”。

为什么要⽤ 3 级缓存 ?我们先看源码执⾏流程,后⾯我会给出答案。

二. 源码解读

注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不⼀样!!!

上⾯的知识,⽹上其实都有,下⾯才是我们的重头戏,让我们一起⾛⼀遍代码流程。

2.1 代码⼊⼝

这⾥需要多跑⼏次,把前⾯的 beanName 跳过去,只看 Model1。

2.2 第⼀层

进⼊ doGetBean(),从 getSingleton() 没有找到对象,进⼊创建 Bean 的逻辑。

进⼊ doCreateBean() 后,调⽤ addSingletonFactory()。

往三级缓存 singletonFactories 塞⼊ model1 的⼯⼚对象。

进⼊到 populateBean(),执⾏ postProcessProperties(),这⾥是⼀个策略模式,找到下图的策略对象。

正式进⼊该策略对应的⽅法。

下⾯都是为了获取 model1 的成员对象,然后进⾏注⼊。

进⼊ doResolveDependency(),找到 model1 依赖的对象名 model2

需要获取 model2 的 bean,是 AbstractBeanFactory 的⽅法。

正式获取 model2 的 bean。

到这⾥,第⼀层套娃基本结束,因为 model1 依赖 model2,下⾯我们进⼊第⼆层套娃。

2.3 第⼆层

获取 louzai2 的 bean,从 doGetBean(),到 doResolveDependency(),和第⼀层的逻辑完全⼀样,找到 model2 依赖的对象名 model1。

前⾯的流程全部省略,直接到 doResolveDependency()。

正式获取 model1 的 bean。

到这⾥,第⼆层套娃结束,因为 model2 依赖 model1,所以我们进⼊第三层套娃。

2.4 第三层

获取 model1 的 bean,在第⼀层和第⼆层中,我们每次都会从 getSingleton() 获取对象,但是由于之前没有初始 化 model1 和 model2 的三级缓存,所以获取对象为空。

敲重点!敲重点!!敲重点!!!

到了第三层,由于第三级缓存有 model1 数据,这⾥使⽤三级缓存中的⼯⼚,为 model2 创建⼀个代理对象,塞⼊ ⼆级缓存。

这⾥就拿到了 model1 的代理对象,解决了 model2 的依赖关系,返回到第⼆层。

2.5 返回第⼆层

返回第⼆层后,model2 初始化结束,这⾥就结束了么?⼆级缓存的数据,啥时候会给到⼀级呢?

甭着急,看这⾥,还记得在 doGetBean() 中,我们会通过 createBean() 创建⼀个 model2 的 bean,当 model2 的 bean 创建成功后,我们会执⾏ getSingleton(),它会对 model2 的结果进⾏处理。

我们进⼊ getSingleton(),会看到下⾯这个⽅法。

这⾥就是处理 model2 的 ⼀、⼆级缓存的逻辑,将⼆级缓存清除,放⼊⼀级缓存。

2.6 返回第⼀层

同 2.5,model1 初始化完毕后,会把 model1 的⼆级缓存清除,将对象放⼊⼀级缓存。

到这⾥,所有的流程结束,我们返回 model1 对象。

三. 原理深度解读

3.1 什么要有 3 级缓存 ?

这是⼀道⾮常经典的⾯试题,前⾯已经告诉⼤家详细的执⾏流程,包括源码解读,但是没有告诉⼤家为什么要⽤ 3 级缓存?

这⾥是重点!敲⿊板!!!

我们先说“⼀级缓存”的作⽤,变量命名为 singletonObjects,结构是 Map,它就是⼀个单例池, 将初始化好的对象放到⾥⾯,给其它线程使⽤,如果没有第⼀级缓存,程序不能保证 Spring 的单例属性

“⼆级缓存”先放放,我们直接看“三级缓存”的作⽤,变量命名为 singletonFactories,结构是 Map>,Map 的 Value 是⼀个对象的代理⼯⼚,所以“三级缓存”的作⽤,其实就是⽤来存放对象的代 理⼯⼚

那这个对象的代理⼯⼚有什么作⽤呢,我先给出答案,它的主要作⽤是存放半成品的单例 Bean,⽬的是为了“打破 循环”,可能⼤家还是不太懂,这⾥我再稍微解释⼀下。

我们回到⽂章开头的例⼦,创建 A 对象时,会把实例化的 A 对象存⼊“三级缓存”,这个 A 其实是个半成品,因为没 有完成依赖属性 B 的注⼊,所以后⾯当初始化 B 时,B ⼜要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这⾥描述,其实也不完全准确,因为不是直接拿,为了让⼤家好理解,我就先这样描述),打破循环

那我再问⼀个问题,为什么“三级缓存”不直接存半成品的 A,⽽是要存⼀个代理⼯⼚呢 ?答案是因为 AOP。

在解释这个问题前,我们看⼀下这个代理⼯⼚的源码,让⼤家有⼀个更清晰的认识。

直接找到创建 A 对象时,把实例化的 A 对象存⼊“三级缓存”的代码,直接⽤前⾯的两幅截图。

下⾯我们主要看这个对象⼯⼚是如何得到的,进⼊ getEarlyBeanReference() ⽅法。

最后⼀幅图太重要了,我们知道这个对象⼯⼚的作⽤:

  • 如果 A 有 AOP,就创建⼀个代理对象;
  • 如果 A 没有 AOP,就返回原对象。

那“⼆级缓存”的作⽤就清楚了,就是⽤来存放对象⼯⼚⽣成的对象,这个对象可能是原对象,也可能是个代理对 象。

我再问⼀个问题,为什么要这样设计呢?把⼆级缓存⼲掉不⾏么 ?我们继续往下看。

3.2 能⼲掉第 2 级缓存么 ?

@Service
public class A {
    @Autowired
    private B b;
    @Autowired
    private C c;
    public void test1() {
    }
}

@Service
public class B {
    @Autowired
    private A a;
    public void test2() {
    }
}

@Service
public class C {
    @Autowired
    private A a;
    public void test3() {
    }
}
复制代码

根据上⾯的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要进⾏ AOP,因为代理对象每次都是⽣成不同的对象,如果⼲掉第⼆级缓存,只有第⼀、三级缓存:

  • B 找到 A 时,直接通过三级缓存的⼯⼚的代理对象,⽣成对象 A1。
  • C 找到 A 时,直接通过三级缓存的⼯⼚的代理对象,⽣成对象 A2。

看到问题没?你通过 A 的⼯⼚的代理对象,⽣成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个⼆级缓存,把 A1 存下来,下次再获取时,直接从⼆级缓存获取,⽆需再⽣成新的代理对象。

所以“⼆级缓存”的⽬的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。

如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满⾜要求。

4. 总结

我们再回顾⼀下 3 级缓存的作⽤:

  • ⼀级缓存:为“Spring 的单例属性”⽽⽣,就是个单例池,⽤来存放已经初始化完成的单例 Bean;
  • ⼆级缓存:为“解决 AOP”⽽⽣,存放的是半成品的 AOP 的单例 Bean;
  • 三级缓存:为“打破循环”⽽⽣,存放的是⽣成半成品单例 Bean 的⼯⼚⽅法。

如果你能理解上⾯我说的三条,恭喜你,你对 Spring 的循环依赖理解得⾮常透彻!

关于循环依赖的知识,其实还有,因为篇幅原因,就不再写了,这篇⽂章的重点,⼀⽅⾯是告诉⼤家循环依赖的 核⼼原理,另⼀⽅⾯是让⼤家⾃⼰去 debug 代码,跑跑流程,挺有意思的。

这⾥说⼀下我看源码的⼼得:

  1. 需要掌握基本的设计模式;
  2. 看源码前,最好能找⼀些理论知识先看看;
  3. 英语能力,学会读英⽂注释,不会的话就百度翻译;
  4. debug 时,要克制⾃⼰,不要陷⼊⽆⽤的细节,这个最重要。

其中最难的是第 4 步,因为很多同学看 Spring 源码,每看⼀个⽅法,就想多研究研究,这样很容易被绕进去了, 这个要学会克制,有全剧意识,并能分辨哪⾥是核⼼逻辑,⾄于如何分辨,可以在⽹上先找些资料,如果没有的话, 就只能多看代码了。

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

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

相关文章

记录--elementui源码学习之仿写一个el-button

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 本篇文章记录仿写一个el-button组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他…

Unity iOS 无服务器做一个排行榜 GameCenter

排行榜需求解决方案一(嗯目前只有一)UnityEngine.SocialPlatformsiOS GameCenterAppStoreConnect配置Unity 调用(如果使用GameCenter系统的面板,看到这里就可以了)坑(需要获取数据做自定义面板的看这里)iOS代码Unity 代码吐槽需求 需求:接入…

某某游戏加密坐标分析

这个游戏里面坐标有很多种存放方式。 例如明文存放的DOUBLE,加密的各种类型。 我们不知道哪一个对于我们是有用的,哪一些只是辅助UI或则掉到LUA虚拟机坑里的数据。 那就根据作用大小来决定,一一尝试吧。 最好去找修改之后有效果的地址,当然只是本地&…

记一次影视cms黑盒CSRF-RCE

俗话说得好,思路才是最重要,本文章主要提供思路,各位师傅在挖掘漏洞的时候说不定也能碰到类似的点1.思路:当我们在找可以构建csrf的时候,多找找可以提交上传图片的,部分是可以自由构建url如图:漏…

Python数据分析案例20——我国家庭资产影响因素分析

本次案例较为简单,符合人文社科、经济学管理学等专业本科生适用。 本文的数据来源于中国家庭金融调查(China Household Finance Survey,CHFS)是西南财经大学中国家庭金融调查与研究中心(下称中心)在全国范围…

后端快速上手Vue+axios

文章目录前言vue基础1.el:挂载点2.data:数据对象vue常见指令vue生命周期axiosvueaxios前言 面向后端人员,旨在快速熟悉Vue框架,更详细的以后再总结 (1)Vue的特性: JavaScript框架简化Dom操作响应式数据驱动 &#…

JWT详细介绍使用

一、JWT介绍 JWT是JSON Web Token的缩写,即JSON Web令牌,是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务…

【经验分享】电路板上电就挂?新手工程师该怎么检查PCB?

小伙伴们有没有经历过辛辛苦苦,加班加点设计的PCB,终于搞定下单制板。接下来焦急并且忐忑地等待PCB板到货,焊接,验证,一上电,结果直接挂了... 连忙赶紧排查,找问题。最终发现,是打过…

学习笔记:基于SpringBoot的牛客网社区项目实现(二)之Spring MVC入门

1.1 函数的返回值为空,因为可以使用response对象向浏览器返回数据。声明了request对象和response对象,dispatcherservlet自动将这两个对象传入 RequestMapping("/http")public void http(HttpServletRequest request, HttpServletResponse re…

ReentranLock(可重入锁)

一、ReentranLock ReentranLock属于JUC并发工具包下的类,相当于 synchronized具备如下特点 ● 可中断 ● 可以设置超时时间 ● 可以设置为公平锁(防止线程出现饥饿的情况) ● 支持多个条件变量 与 synchronized一样,都支持可重…

浅析 SplitChunksPlugin 及代码分割的意义

本文作者为 360 奇舞团前端开发工程师起因有同事分享webpack的代码分割,其中提到了SplitChunksPlugin,对于文档上的描述大家有着不一样的理解,所以打算探究一下。Q:什么是 SplitChunksPlugin?SplitChunksPlugin 是用来…

Python所有方向的入门和进阶路线,20年老师傅告诉你方法

干了20多年程序员,对于Python研究一直没停过,这几天把我自己对Python的认知和经验,再结合很多招聘网站上的技术要求,整理出了Python所有方向的学习路线图,基本上各个方向应该学什么,都在上面了,…

macOS 13.3 Beta 3 (22E5236f)发布

系统介绍3 月 8 日消息,苹果今日向 Mac 电脑用户推送了 macOS 13.3 开发者预览版 Beta 3 更新(内部版本号:22E5236f),本次更新距离上次发布隔了 7 天。macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力…

文件预览kkFileView安装及使用

1 前言网页端一般会遇到各种文件,比如:txt、doc、docx、pdf、xml、xls、xlsx、ppt、pptx、zip、png、jpg等等。有时候我们不想要把文件下载下来,而是想在线打开文件预览 ,这个时候如果每一种格式都需要我们去写代码造轮子去实现预…

k8s pod调度总结

在Kubernetes平台上,我们很少会直接创建一个Pod,在大多数情况下会通过控制器完成对一组Pod副本的创建、调度 及全生命周期的自动控制任务,如:RC、Deployment、DaemonSet、Job 等。本文主要举例常见的Pod调度。1全自动调度功能&…

第二章:基础语法

第二章:基础语法 2.1:关键字和保留字 关键字 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词) 特点:关键字中所有字母都为小写 分类: 用于定义数据类型的关键字 class、interface、enum、byt…

算法设计与分析——递归与分治策略——全排列Perm函数

删除线格式 [toc] 问题描述 现给出m个不同的数字,在n个位置上,对齐进行全排列。使用编程实现数学中全排列输出最终计算结果并将所有的排列打印出来。 思路分析 常规的递归方式进行解决即可,递归的终点是根据题目要求进行实现。共有两个参…

第一次运行vue遇到的问题

1.vue无法识别https://blog.csdn.net/weixin_61634408/article/details/1265897982.yarn serve问题https://blog.csdn.net/fangxuan1509/article/details/104711690/3.关闭控制台报错检查(每次vue-rounter必须用)vue.config,js,的module.exports 中添加l…

【Linux】sudo指令

在本期博客正式开始之前,我们先来解决一个历史遗留问题:sodu指令怎么用不了?sudo指令📌sudo是linux下常用的允许普通用户使用超级用户权限的工具,允许系统管理员让普通用户执行一些或者全部的root命令📋但是…

【预告】ORACLE Unifier v22.12 虚拟机发布

引言 离ORACLE Primavera Unifier 最新系统 v22.12已过去了3个多月,应盆友需要,也为方便大家体验,我近日将构建最新的Unifier的虚拟环境,届时将分享给大家,最终可通过VMWare vsphere (esxi) / workstation 或Oracle …