Spring的循环依赖

本文简要介绍了循环依赖以及Spring解决循环依赖的过程

循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环。这里的对象特指单例对象。

对象之间的循环依赖主要有两种表现形式:构造函数循环依赖和属性循环依赖。

2-1 构造函数循环依赖

A, B,之间形成的循环依赖,具体表现在:A构造函数的参数是B,B构造函数的参数是A,即构造函数循环依赖。

将这2个bean交给Spring容器管理,并使用构造函数注入来模拟构造函数循环依赖。

测试构造函数循环依赖:

测试结果:

这种构造函数循环依赖无法解决,因为JVM在对类进行实例化时,需要先实例化构造函数的参数,而由于各个构造函数的参数之间是循环依赖的,所以各个参数都无法提前实例化,故只能抛出错误。

2-2 属性循环依赖

A, B,之间形成的循环依赖,具体表现在:A一个属性是B,B一个属性是A,即属性循环依赖。

将这2个bean交给Spring容器管理,并使用属性注入来模拟属性循环依赖。

测试属性循环依赖:

测试结果:

这种表现形式的循环依赖Spring可以解决,大致的思路是:Spring先使用各个类的无参构造函数实例化各个对象,再使用Setter去设置对象的属性。但Spring在实例化各个对象时并不是按定义顺序去实例化的,中间还穿插了递归实例化的过程。

假定有A ,B, C三个类,A, C之间存在着循环依赖关系(即A有个C类型的属性,C有个A类型的属性),XML配置文件中定义的顺序为A->B->C。

Spring首先会去实例化A,A实例化完成后,为A注入其属性C的值,在注入属性C的时候发现C还未被实例化就先去实例化C,实例化C完成后,为C注入属性A,而此时A已经完成实例化了,所以可以直接为C注入属性A,然后完成对A中的C属性注入,最后再去实例化B。所以实例化顺序是A-> C->B而不是A->B->C。

在某个对象创建的过程中,如果递归调用回来发现自己正在创建过程中的话,即说明存在循环依赖。

Spring解决循环依赖的主要依据是Java的引用传递:当我们获取到对象的引用时,对象的属性可以是还未设置的(允许延后设置属性),但构造器的执行必须在获取引用之前。

Spring单例对象的初始化主要分为3步:

第1步 实例化:调用对象的构造函数实例化对象;

第2步 注入属性:对实例化对象的属性值进行注入;

第3步 初始化:调用SpringXML中的init()方法。

由上述初始化步骤可知,循环依赖主要发生在第1,2 步,即:构造器循环依赖和属性循环依赖。Spring解决属性循环依赖使用了三级缓存。

Spring版本信息为:

5-1 三级缓存

三级缓存的源码(调换了顺序):

三级缓存:

一级缓存->singletonObjects:存放的是初始化之后的单例对象

二级缓存->earlySingletonObjects:存放的是一个已完成实例化未完成初始化的早期单例对象

三级缓存->singletonFactories:存放的是可产生单例对象的工厂对象(此缓存中的bean name和二级缓存中的bean name是互斥的,即此级缓存中有某个bean的工厂,二级缓存中就没有这个bean)

5-2 具体实现

对于非懒加载的类,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包扫描以及bean的初始化。

此处调用了beanFactory的一个方法preInstantiateSingletons(),此处的beanFactory是DefaultListableBeanFactory。

DefaultListableBeacFactory的preInstantiateSingletons()方法:

在此方法中,循环Spring容器中的所有Bean,依次对其进行初始化,初始化的入口就是getBean()方法。

追踪getBean方法:

引用了AbstractBeanFacotry中重载的doGetBean()方法:

追踪doGetBean()方法:

该方法中使用的方法① 、方法②、方法③对解决循环依赖起了至关重要的作用,下面来依次分析:

方法①:getSingleton(String beanName, boolean allowEarlyReference)

通过上面的步骤可以看出这三级缓存的优先级。其中singletonObjects里面存放的是初始化之后的单例对象;earlySingletonObjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonFactories中存放的是ObjectFactory对象,此对象的getObject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。

当debug到此处时,以2-2中的A, B为例,第一步走到这个方法的是A,此时从这三个缓存中获取到的都是null,因为还没有内容被添加到这三级缓存中去。这个方法主要是给循环依赖中后过来的对象使用。

方法②:getSingleton(String beanName, ObjectFactory singletonFactory)

获取单例对象的主要逻辑就是次方法实现的,主要分为上面4个步骤:

步骤A:

首次将beanName放入到singletonCurrentlyInCreation中

步骤C:

得到单例对象后,再讲beanName从singletonsCurrentlyInCreation中移除

步骤D:

步骤B:

此处调用了ObjectFactory的getObject方法,此方法是在方法③中实现的

步骤B其实也就是 方法③

去掉无关代码后,关键方法只有doCreateBean() 方法

doCreateBean() 方法:

上面代码中[★重要]标注的重点是此方法的关键。在addSingletonFacotry()方法中,将< beanName->能创建该bean的工厂>键值对添加到了三级缓存中以供其他对象依赖时调用。

populateBean()方法对刚实例化的bean进行属性注入,如果遇到要注入的属性对应的值是由Spring管理的bean,则再通过getBean方法获取该bean。

5-3 总结

属性注入主要是在populateBean() 方法中进行的。对于循环依赖,以2-2中的A, B为例,假定Spring的加载顺序为先加载A。其具体的加载过程为:

参考源:
1. https://www.cnblogs.com/leeego-123/p/12165278.html
2. https://blog.csdn.net/qq_36381855/article/details/79752689

Original: https://www.cnblogs.com/certainTao/p/14405590.html
Author: certainTao
Title: Spring的循环依赖

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/711762/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • FreeTpye库学习笔记:将矢量字体解析为位图

    近期工作需要研究 FreeType 库,因此本篇文章记录一下该库的基本用法。 FreeType 是一个免费、开源、可移植且高质量的字体引擎,它有以下优点: 但 FreeType 也…

    技术杂谈 2023年6月1日
    0103
  • 想学嵌入式?要不一起玩 Arduino 吧

    作者:HelloGitHub- Anthony 这里是 HelloGitHub 推出的《讲解开源项目》系列,本期介绍的是如何用开源硬件开发平台 Arduino,自己动手做一个温湿度…

    技术杂谈 2023年6月1日
    0109
  • 查询3天内创建邮箱的用户

    查询3天内创建邮箱的用户 $d = (Get-Date).adddays(-3) $ys = Get-ADUser -Filter ‘Enabled -eq $true -and …

    技术杂谈 2023年5月31日
    096
  • 「周记」拓扑排序

    拓扑排序的英文名是 Topological sorting。拓扑排序要解决的问题是给一个图的所有节点排序。拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点…

    技术杂谈 2023年7月24日
    070
  • 20天等待,申请终于通过,安装和体验IntelliJ IDEA新UI预览版

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于IDEA的预览版 IDEA会启…

    技术杂谈 2023年7月10日
    0103
  • Git&Gitee

    Git Git介绍 安装 命令学习 Git的作用 git与svn比较 Git,GitHub,GitLab,Gitee Git工作流程 Git常用命令 在仓库目录终端下 &#8211…

    技术杂谈 2023年6月21日
    099
  • Redis变慢?深入浅出Redis性能诊断系列文章(四)

    (本文首发于”数据库架构师”公号,订阅”数据库架构师”公号,一起学习数据库技术,助力职业发展) 本篇为Redis性能问题诊断系列的第…

    技术杂谈 2023年7月25日
    082
  • centos 7 无痛安装 es

    1、装依赖 yum install -y java-1.8.0-openjdk 2、安装elasticsearch 目录创建 mkdir -p /opt/{soft,conf,lo…

    技术杂谈 2023年5月30日
    0116
  • 容器不能使用 ps 等命令了 如何处理

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年5月31日
    0107
  • 云原生技术持久化存储PV与PVC

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年5月31日
    0109
  • MySQL数据库-数据表(一)

    数据表的基本操作. MySQL 数据库支持多种数据类型,大致可以分为 3 类:数值类型、日期和时间类型、字符串(字符)类型。 (1)数值类型 数值类型用于存储数字型数据,这些类型包…

    技术杂谈 2023年6月21日
    0112
  • tarjan全家桶

    tarjan 全家桶 关于tarjan 它太强了 CCCOrz dfs树&low dfs树:在图上做不重复经过同一点的dfs,经过的边与点形成一棵树。于是图上所有点都被这棵…

    技术杂谈 2023年6月21日
    098
  • Codeforces Round #753 (Div. 3)

    Codeforces Round #753 (Div. 3) A. Linear Keyboard 思路分析: 无语了,题目总是读不顺,看到output那个minimal我还以为是…

    技术杂谈 2023年7月24日
    088
  • Spring Ioc源码分析系列–容器实例化Bean的四种方法

    Spring Ioc源码分析系列–实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列–Bean实例化过程(二)在讲解到bean真正通…

    技术杂谈 2023年7月25日
    085
  • 实战模拟│JWT 登录认证

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年7月11日
    061
  • js中常用的语法

    一、注释 二、输出 输出有三种: 三、变量 概述:变量是在内存中生成一个空间用来存储数据。 1.声明变量 var age; 2.同时声明多个变量,使用逗号隔开。 Original:…

    技术杂谈 2023年5月31日
    0112
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球