【源码+个人总结】Spring 的 三级缓存 解决 循环依赖

news2025/1/15 7:42:26

Spring可以通过以下方法来避免循环依赖:

  1. 构造函数注入:使用构造函数注入来注入依赖项,这是一种比较安全的方式,因为在对象创建时就会注入依赖项,可以避免循环依赖。

  2. Setter方法注入:使用Setter方法注入依赖项,Spring会在对象创建后调用Setter方法来注入依赖项,这种方式也可以避免循环依赖。

  3. 使用@Lazy注解:使用@Lazy注解来延迟依赖项的注入,这样可以避免循环依赖。

  4. 使用@DependsOn注解:使用@DependsOn注解来指定bean创建的顺序,可以避免循环依赖。

  5. 使用 @Lookup 注解: 在这种情况下,Spring 容器会在每次调用 @Lookup 注解修饰的方法时,返回一个新的 bean 实例。这样,就可以确保两个 bean 之间没有直接的依赖关系。

  6. spring自带的三级缓存【使用代理】:  (默认)使用 AOP 代理来实现 bean 之间的依赖关系。在编译时就解决循环依赖问题。

三级缓存的作用是 Spring IoC 的难点,搞清楚它的原理和背后的原因非常有必要!

前言

spring bean 的 创建三步:
  1. 创建bean类的实例
  2. 注入属性
  3. bean对象的初始化

单例模式

        一般在单例模式中,会使用双重检查锁定(Double-Checked Locking)来保证线程安全。双重检查锁定是为了避免多个线程同时进入临界区,以及提高性能。具体来说:
  1. 第一个检查是为了保证只有少量线程能够进入临界区。这种方式可以减少锁的竞争,提高性能。
  2. 第二个检查是为了保证在多个线程中只有一个线程能够创建实例。
    这样可以避免(多个实例并行请求建立单例对象时,都卡在synchronized锁那里,当一个线程已经创建完实例后,)在实例已经存在的情况下,多个线程继续创建实例,从而浪费资源。
```
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
```
        在上面的代码中,我们使用了双重检查锁定来确保只有一个实例被创建。首先,在第一个检查中,我们检查实例是否已经被创建。如果没有,我们进入同步块。在同步块中,我们再次检查实例是否已经被创建。如果没有,我们创建一个新的实例。由于我们使用了volatile关键字,这确保了在并发环境下的可见性和正确性。

源码

        spring中bean 的创建是由 AbstractBeanFactory#getBean() 触发的。

循环依赖过程分析

AbstractBeanFactory#getBean()  -->  AbstractBeanFactory#doGetBean()  --> DefaultSingletonBeanRegistry#getSingleton()
在触发 bean 的加载时,会先从缓存中获取 bean,也就是会调用 DefaultSingletonBeanRegistry#getSingleton() 方法。
        跟一下 AbstractBeanFactory#getBean(java.lang.String) 的源码,会发现它会调用 DefaultSingletonBeanRegistry#getSingleton()
源码一    DefaultSingletonBeanRegistry#getSingleton() 
从三级缓存中获取单例对象
        默认的单例bean注册器  DefaultSingletonBeanRegistry#getSingleton()  
~~~
// 参数allowEarlyReference:允许早期引用 【默认是true】:
protected Object getSingleton(String beanName,  boolean allowEarlyReference) {
    // 从一级缓存中 快速检查有没有 完全初始化的现有实例  
    Object singletonObject = this. singletonObjects.get(beanName);
    //  类似于单例模式的双锁校验。  一级缓存 中没有 beanName, 且 beanName 正在创建中
    if ( singletonObject  == null && this.isSingletonCurrentlyInCreation(beanName)) {
             synchronized (this. singletonObject) {
                  //现在 二级缓存中查
                  singletonObject = this. earlySingletonObjects.get(beanName);
                 // 判断 等于null才处理,避免多个竞争锁的线程都走一遍创建
                 //(加判定后,都去读二级缓存就可以了),且允许早期引用时才继续
                  if ( singletonObject  == null  &&  allowEarlyReference) {
                        // 先去三级缓存中 查询 ,三级缓存有时才创建!
                        ObjectFactory<?> singletonFactory = this. singletonFactories.get(beanName);
                        if ( singletonFactory != null) {
                            // 获取引用对象存于三级缓存中, 再存入 earlySingletonObjects,并返回此对
                            //象。 对应三级缓存中删除相应工厂类
                            singletonObject = singletonFactory.getObject();
                            this. earlySingletonObjects.put(beanName, singletonObject);
                            this. singletonFactories.remove(beanName);
                        }
                }
        }
    }
    return  singletonObject;
}
~~~
以上就是三级缓存的代码。其中:
  • singletonObjects ** 一级缓存: 
cache of singleton objects: bean name to bean instance. 
     用于存放已经完全初始化好的 bean
  • earlySingletonObjects ** 二级缓存:
 cache of early singleton objects: bean name to bean instance.  
用于存放 bean 的早期引用 只实例化类,还没初始化的对象)( 已经创建,尚未完全初始化的单例对象)(循环依赖时才会存放并使用)
  • singletonFactories ** 三级缓存: 
cache of singleton factories: bean name to ObjectFactory.   
     用于存放三级缓存 ObjectFactory 获取到的工厂对象(都会存放,循环依赖时才会使用)
     批注   
        与 单例模式 同理,在产生循环依赖的问题时, 在二级缓存查询前就外就开始加了 synchronized的重量级锁 锁住了 singletonObject 对象,
当在并发的情况下,其他要 创建 bean /类的实例化  的线程都得处于等待的过程。
当一个线程创建完后,其他线程 判断 等于null才处理,避免多个竞争锁的线程都走一遍创建(加判定后,都去读二级缓存就可以。
这样确保只有一个线程 去走第三缓存并创建bean!
        第三缓存,乍一看并没有什么特殊的意义,因为锁内的第二缓存已经线程安全的!可以用局部变量代替第三缓存(反正第三缓存用后即删了)。硬说与线程安全有和关联的话,可能是 和 三级缓存 singletonFactories 的注入/赋值有什么关联了,但此方法中并没有三级缓存的插入。三级缓存的插入在 同类下的方法中  DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)进行插入的。说明得确保先有对应的工厂类才会去处理并创建bean实例对象。
        个人感觉 此处已经与 线程安全没有啥关系了,和spring的设计思路和思想有关吧~
        此方法中没有三级缓存 singletonFactories 的数据来源,即数值的插入!数据来源在 同类  DefaultSingletonBeanRegistry 下的方法中 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 
源码二    DefaultSingletonBeanRegistry#  addSingletonFactory ()
        现在开始找找 三级缓存 singletonFactories  的数据来源  !!
/**
    如有必要 添加给定的单例工厂以构建指定的单例。 要求对单例进行急切注册,例如能够解析循环引用。
    参数singletonFactory 是 beanName和单例bean的工厂。
*/
这个类的调用在 AbstractAutowireCapableBeanFactory# doCreateBean()中
源码三   AbstractAutowireCapableBeanFactory doCreateBean ()
        在bean类的 实例化之前,就已经开始准备 bean提前引用的对象 了
 // 中间代码忽略。。。。。。。
在 AbstractAutowireCapableBeanFactory# doCreateBean() 中可见:
  1.  实例化bean类  createBeanInstance(beanName, mbd, args);
  2. 。。。。。。
  3. 第三级缓存的数值来源准备, 调用了方法: getEarlyBeanReference():  【源码四 见详情】
  4. populateBaen 装填属性
  5.  获取 单例对象 getSingleton (查 一二三级缓存)
  6. hasDependentBean(beanName)  查询是否有依赖的bean并处理
  7. 。。。。。
  8. 初始化 bean 实例/对象
源码四   AbstractAutowireCapableBeanFactory # getEarlyBeanReference ()
工厂类预先生成代理
getEarlyBeanReference方法,* 获取指定 bean 的早期引用,通常用于解析循环引用。
/**
* Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
   获取引用以便提前访问指定的 Bean,通常用于解析循环引用。
* @param beanName – Bean 的名称(用于错误处理目的)。 the name of the bean (for error handling purposes)
* @param mbd – Bean 的合并 Bean 定义 . the merged bean definition for the bean
* @param bean - – 原始 Bean 实例 。 the raw bean instance
* return :要公开为 Bean 引用的对象。 the object to expose as bean reference
*/
protected Object  getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
       Object exposedObject = bean;
       if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
          for (BeanPostProcessor bp : getBeanPostProcessors()) {
             if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                 SmartInstantiationAwareBeanPostProcessor ibp = ( SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
             }
          }
       }
       return exposedObject;
}
这个工厂的作用就是判断这个对象是否需要代理,如果否 直接返回,如果需要 则创建代理对象 并返回。

这里还有个知识点:

                                        Spring AOP : 自动代理创建机制 (APC)
        一个APC其实是一个SmartInstantiationAwareBean PostProcessor ,  它会介入每个bean的 类实例化 和 对象的初始 ,检测bean的特征。如果该bean符合某些特征,比如有拦截器需要应用到该bean,那么该APC就会为它自动创建一个代理对象,使用相应的拦截器包裹住该bean。


循环依赖 流程图

        在有循环依赖的情况下:(A --> B --> A 的场景)

        A 第一次加载时,会将 A 对应的 ObjectFactory 放到三级缓存中;

        当 B 创建完实例后,进行 populateBean 填充依赖时,会通过 getBean(A) 来获取 bean A,这时会使用 A 对应的三级缓存 ObjectFactory 来获取 bean A 的早期引用。

总结

问题 : spring为什么要用三级缓存?二级缓存不能实现么 ? 
上述: 一级缓存 存 已经完全初始化的bean单例的对象,
           二级缓存 存 类已实例化还初始化的不完全对象,
           三级缓存 存的是创建类实例的工厂类对象(这一级缓存的时间还早于装填属性,在检测到循环依赖之前。)
        在二级缓存没有时,再去获取这个工厂类对象,并将结果存于二级缓存也是可行的!但是:
        正常的代理的对象初始化后期调用生成的,是基于后置处理器PostProcessor的,若提早的代理就违背了Bean定义的生命周期。所以spring在一个三级缓存放置一个工厂,如果产生循环依赖 ,那么就会调用这个工厂提早的得到代理的对象。
       

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

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

相关文章

云开发谁是卧底线下小游戏发牌助手微信小程序源码

源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/87614365 云开发谁是卧底线下小游戏源码&#xff0c;发牌助手微信小程序源码。 “谁是卧底OL”是一个非常有趣&#xff0c;风靡全国的比拼语言表述能力、知识面与想象力的游戏。 谁是卧底OL是一款由开发…

Notepad++下载安装NppFTP插件

文章目录 一、Notepad内下载安装NppFTP插件1.打开Notepad——插件——插件管理2.找到NppFTP插件——点击安装3.安装完成 二、直接下载安装NppFTP插件1.网盘下载2.将下载好的NppFTP插件放入到Notepad\plugins的插件目录下3.重启Notepad 三、Notepad下载总结 一、Notepad内下载安…

【刷题】142. 环形链表 II

142. 环形链表 II 一、题目描述二、示例三、实现3.1 方法13.2 方法2 142. 环形链表 II 一、题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 nex…

前端学习 - 淼哥学Vue

如何判断数据是否受Vue管理&#xff0c;数据&#xff08;对象&#xff0c;数组&#xff0c;字符串等&#xff09;能否响应式更新&#xff1f; 即查看数据是否有对应get/set方法&#xff0c;数组没有对应get/set方法&#xff0c;故操作数组要通过其封装好的变更方法 变更方法 …

js高级知识汇总一

目录 1.怎么理解闭包&#xff1f; 2.闭包的作用&#xff1f; 3.闭包可能引起的问题&#xff1f; 4.变量提升 5.函数动态参数 6.剩余参数 ...&#xff08;实际开发中提倡使用&#xff09; 7.展开运算符 8.箭头函数 9.解构赋值&#xff08;数组、对象&#xff09; 10 创…

一文总结动态规划

动态规划 一、背包问题1 问题定义2 问题分类3 解题模板01背包最值问题剩余背包问题 4 例题分析LeetCode1049.最后一块石头的重量II 二、区间动态规划1 解题模板2 例题分析牛客.石子合并 总结与分析 一、背包问题 1 问题定义 如何确定一个题目是否可以用背包问题解决 背包问题…

给儿童使用护眼台灯怎么样选择更好?专家建议孩子买台灯

随着娃越长越大&#xff0c;虽然还在读幼儿园&#xff0c;但平时免不了要写写画画&#xff0c;之前一直在这个桌子上&#xff0c;台灯是一个赠送的LED货色&#xff0c;那个频闪啊&#xff0c;于是趁着当地商场活动先入了张学习桌椅&#xff0c;至于台灯嘛当然要选个好的了&…

21财经专访徐亚波博士:AI恒纪元时代,数说故事踏浪新征途

21世纪经济报道【创业投资】栏目&#xff0c;一直致力于寻找中国最有生命力和创造力的快速成长公司&#xff0c;探秘其背后的新兴资本推动力。为此&#xff0c;数说故事创始人兼CEO徐亚波博士接受了21世纪经济报道的专访。 近年来&#xff0c;大数据产业已经成为推动数字经济发…

【C语言】都玩过三子棋游戏把,但你知道怎么用C语言实现三子棋游戏吗?让我来手把手教你。

三子棋游戏 1.前言2.功能分析2.1主函数设计及菜单设计2.2打印棋盘与棋盘初始化2.3玩家下棋2.4电脑下棋2.5判断输赢 3.game.h头文件展示4.text.c源文件文件展示5.game.c源文件文件展示 所属专栏&#xff1a;C语言 博主首页&#xff1a;初阳785 代码托管&#xff1a;chuyang785 感…

Android 开发中高阶函数的 10 个实例

Android 开发中高阶函数的 10 个实例 Kotlin 是一种现代编程语言&#xff0c;由于其表现力、简洁性和多功能性而变得越来越流行。它的关键特性之一是支持高阶函数&#xff0c;这使您可以编写更简洁、更灵活的代码。高阶函数是一种将一个或多个函数作为参数或返回一个函数作为结…

python+java+nodejs基于vue的企业人事工资管理系统

根据系统功能需求分析&#xff0c;对系统功能的进行设计和分解。功能分解的过程就是一个由抽象到具体的过程。 作为人事数据库系统&#xff0c;其主要实现的功能应包括以下几个模块&#xff1a; 1.登录模块 登录模块是由管理员、员工2种不同身份进行登录。 2.系统管理模块 用户…

工具抓包Charles配置HTTPS步骤

charles抓取HTTPS设置&#xff0c;详细踩坑版 写这篇文章的背景就是&#xff0c;每次我在一台新电脑上用charles抓包时&#xff0c;总是因为各种原因无法抓到https请求&#xff0c;每个百度出来的回答又不是那么详细&#xff0c;需要通过几篇回答才能解决过程中的各种问题&…

C++程序员的职业前景怎么样?来谈谈我自己的想法

我之前提到了程序员在二线城市的大概待遇。今天&#xff0c;我要说一下普通程序员的职业前景。因为最初阶段的工资可能比较高&#xff0c;但如果没有可持续性&#xff0c;这就不是一个特别好的工作。 从我自身的经验来看&#xff0c;我们公司的程序员主要有两条路线。一条是纯…

【存储数据恢复】NetApp存储WAFL文件系统数据恢复案例

存储数据恢复环境&#xff1a; NetApp存储设备&#xff0c;WAFL文件系统&#xff0c;底层是由多块硬盘组建的raid磁盘阵列。 存储故障&#xff1a; 工作人员误操作导致NetApp存储内部分重要数据被删除。 存储数据恢复过程&#xff1a; 1、将存储设备的所有磁盘编号后取出&…

软考A计划-常用公式复习

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

使用Nginx做反向代理

使用Nginx做反向代理 文章目录 使用Nginx做反向代理代理HTTP请求代理HTTPS请求举个大栗子 代理HTTP请求 按照以下步骤使用Nginx做反向代理&#xff1a; 编辑 Nginx 的配置文件。默认情况下&#xff0c;Nginx 的配置文件位于 /etc/nginx/nginx.conf。 sudo nano /etc/nginx/ngi…

1703_LibreOffice常用功能使用体验

全部学习汇总&#xff1a; GreyZhang/windows_skills: some skills when using windows system. (github.com) 首先需要说明的是我不是一个重度Office用户&#xff0c;甚至算不上一个重度的Office用户。我使用的Office软件最多的功能就是文档编辑&#xff0c;绝大多数时候还是文…

【什么是苹果推信?什么是苹果推?】通过苹果手机Imessage进行信息推送的方式;

如今不少人都在利用苹果手机&#xff0c;重要是装备高端&#xff0c;很少呈现卡机的征象&#xff0c;并且星移斗换快&#xff0c;紧跟互联网期间成长的脚步。苹果手机是火了&#xff0c;谁又能想到另有比它更火的事变出现呢&#xff0c;便是苹果推信。苹果推信主要上风是推信群…

晋商银行“沧海”数据资产管理系统

案例名称 晋商银行“沧海”数据资产管理系统 案例简介 晋商银行“沧海”数据资产管理系统&#xff0c;取自“海纳百川、沧海一粟”之意&#xff0c;即数据如茫茫大海&#xff0c;其价值不可估量。该系统贯穿数据的全生命周期&#xff0c;包括数据多维度描述、数据…

期末复习自用--python

前言 python的优点&#xff1a; 简洁&#xff0c;语法优美&#xff0c;简单易学&#xff0c;开源&#xff0c;可移植性好&#xff0c;拓展性好&#xff0c;类库丰富&#xff0c;通用灵活&#xff0c;模式多样&#xff0c;良好的中文支持。 python的缺点&#xff1a; 执行效率不…