spring源码 循环依赖

news2024/9/17 8:36:39

spring框架两大核心:IOC和AOP

IOC(Inverse of Control)控制反转

将对象的创建权交给 Spring 容器去创建,利用了工厂模式将对象交给容器管理,只需要在spring配置文件中配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。不需要我们手动new去创建对象,大大降低了代码间的耦合度,使资源更加容易管理。

AOP(Aspect Oriented Programming)面向切面编程

通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。

循环依赖

问题描述

A有属性b,B有属性a,创建A时对b属性赋值需要有b对象,如果没有b就创建,但是创建B又需要对属性a赋值,这样就构成了循环依赖

前置知识

Spring在创建Bean的过程中分为三步

实例化 ,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

属性注入,对应方法:AbstractAutowireCapableBeanFactorypopulateBean方法

初始化,对应方法: AbstractAutowireCapableBeanFactoryinitializeBean方法

其中AOP是在初始化阶段完成的

解决方案:三级缓存(3个Map)

核心思想是:bean进行实例化操作后,不需要进行依赖注入(属性赋值)操作就放入缓存中,当其他bean需要时直接从缓存中拿就行了

  • 一级缓存为:singletonObjects 缓存已经创建好的bean对象(完成了实例化,属性注入,初始化)。
  • 二级缓存为:earlySingletonObjects 缓存只完成了实例化,但是还未进行属性注入及初始化的对象
  • 三级缓存为:singletonFactories 提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

利用三级缓存解决循环依赖的流程(假设A对象中有属性b,B对象中有属性a)

首先创建A对象,先后查询一级,二级,三级缓存,都没有A这个bean对象,所以先进行实例化,实例化完成后存到三级缓存中,然后进行属性注入,A类只有一个类型为B的属性b,先后查询一级,二级,三级缓存,都没有B这个bean对象,先实例化,实例化后存到三级缓存中,进行属性注入,B类只有一个类型为A的属性a,查一级缓存,没有,查二级缓存,没有,查三级缓存,找到提前暴露的工厂,通过工厂创建A对象,将三级缓存中的创建A对象的工厂删除(remove),将创建好的A对象存进二级缓存,然后赋值给B类的a属性,B类进行初始化,此时已经创建完成,就赋值给A类的b属性,A类进行初始化,创建完成。

现在有这样一个经典的循环依赖(先不考虑AOP)

执行流程:

首先是创建A:

在getSingleton(beanName,true)方法中,依次查询一二三级缓存,发现都没有beanName为A的映射,就跳转到重载方法getSingleton(beanNeme, singletonFactory)中,这个方法的用途是创建bean,因为缓存中都没有,所以要新创建这个A对象。但是并不是简单调用createBean这个方法就完了,还要将创建好的bean(完成实例化,属性注入,初始化)存进一级缓存singletonObjects

来看下getSingleton(beanName,singletonFactory)方法源码:(看蓝色重点部分)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {

            // ....
            // 省略异常处理及日志
            // ....

            // 在单例对象创建前先做一个标记
            // 将beanName放入到singletonsCurrentlyInCreation这个集合中
            // 标志着这个单例Bean正在创建
            // 如果同一个单例Bean多次被创建,这里会抛出异常
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
                // ...
                // 省略catch异常处理
                // ...
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 添加到一级缓存singletonObjects中
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

这里的getObject()方法会调用createBean方法

真正完成这些的是doCreateBean方法

在doCreateBean方法中:

先进行实例化,实例化后对实例化后的bean对象封装成一个对象工厂,存进三级缓存singletonFactorys,然后才进行属性注入和初始化

// Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        //向容器中缓存单例模式的Bean对象,以防循环引用
        //判断是否是早期引用的bean,如果是,则允许其提前暴露引用
        // 这里判断的逻辑主要有三个∶
        //1.是否为单例
        //2 是否允许循环引用
        //3.是否是在创建中的bean
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            //这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用
            //将还没完全配置好的bean存入到三级缓存中供其他bean使用(暴露引用)
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
            //这里的getEarlyBeanReference()方法,并不是在此调用,而是声明好了方法
            //具体的调用在Spring的AbstractBeanFactory的doGetBean的第三行调用
            // DefaultSingletonBeanRegistry类的getSingleton方法中,调用
            // singletonFactory.getObject()(单例工厂的getObject方法返回实例对象)
        }
 
        // Initialize the bean instance.
        //Bean对象的初始化,依赖注入在此触发
        //这个exposedObject在初始化完成之后返回作为依赖注入完成后的Bean
        Object exposedObject = bean;//暴露的对象
        try {
            //属性注入
            populateBean(beanName, mbd, instanceWrapper);
 
            //初始化bean,过程如下:
            //1.判断是否实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware方法,
            //如果有,则设置相关的属性
            //2.调用bean初始化前的前置(BeanPostProcessor)操作
            //3.执行初始化的方法
            //如果有InitializingBean,则调用afterPropertiesSet
            //如果有InitMethod,则调用初始方法
            //4.调用bean初始化的后置(BeanPostProcessor)操作
            
            //初始化
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }

这里将实例化后的bean添加到三级缓存singletonFactorys调用了方法:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这里的第二个参数是一个lambda表达式 () -> getEarlyBeanReference(beanName, mbd, bean),

这个addSingletonFactory是什么呢?

在通过三级缓存得到beanName对应的工厂后(objectFactory),调用getObject方法来得到对象,这个对象实际上就是通过getEarlyBeanReference方法创建的。

addSingletonFactory方法源码:

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

addSingletonFactory方法源码:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    //如果要实现AOP
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

回到流程中

此时A这个bean已经实例化好并且存进三级缓存singletonFactorys中,下一步是属性注入,A中只有B类型的b这一个属性,和创建A时一样,还是

getBean->doGetBean->getSingleton(bean,true)->getSingleton(b,ObjectFactory)

->createBean->doCreateBean

实例化后存到三级缓存中,进行属性注入,B里面只有A类型的a这一个属性,依次查询一二三级缓存,然后在第三级缓存中查询到A,得到A的工厂,调用getObject()方法得到只进行了实例化的a,将a赋值给B,再进行初始化,此时B已经创建完成,将B赋值给A的b属性,A进行初始化,此时A也创建完成。

这里还要注意:从三级缓存中获取完之后,会将工厂得到的对象存进二级缓存earlySingletonObjects,然后再三级缓存中删除该beanName对应的映射。

考虑AOP的情况

如果启用了AOP,那么在A的创建过程中:

在实例化后进行存入三级缓存时,存的是代理后的对象

在B的创建过程中:

在属性注入时,从三级缓存中获取A得到的也是代理后的对象

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;
}
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

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

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

相关文章

社区养老服务小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;服务人员管理&#xff0c;服务产品管理&#xff0c;服务预约管理&#xff0c;服务状态管理&#xff0c;服务退订管理&#xff0c;活动管理&#xff0c;视频管理 微信端账号功能包…

单线程 和多线程区别,看打印输出1000个数字效果

执⾏过程: 加载func() -> 执⾏main -> 创建⼦线程t -> ⼦线程t启动 -> 执⾏func中的内容 |-> 继续执⾏main from threading import Thread #此线程不用安装自带。T是大写注意哟 def func():for i in range(1000):print(func,i) #定义一个函数打印 if __name__ …

因子分析★★★★★

该博客为个人学习清风建模的学习笔记&#xff0c;代码全部摘自清风老师&#xff0c;部分课程可以在B站&#xff1a;【强烈推荐】清风&#xff1a;数学建模算法、编程和写作培训的视频课程以及Matlab等软件教学_哔哩哔哩_bilibili 该博文可以与主成分分析一起看&#xff0c;博主…

Microsoft Print To PDF如何打印到网络地址

正常情况下&#xff0c;Microsoft Print To PDF是仅提供本地PDF打印功能的。不过还是可以通过配置&#xff0c;打印到网络地址。 这里需要用到文件共享功能 假设我们要使用Microsoft Print To PDF从A电脑打印到B电脑的D:\output文件夹。操作方法如下 1、打开D:\output文件夹属…

结合Java代码实现RocketMQ的生产与消费消息

前言 在前面的文章中&#xff0c;已经详细介绍并使用到了消息生产者&#xff0c;消息消费者&#xff0c;broker等集群相关的知识&#xff0c;这篇文章介绍一下其他的小组件以及使用Java代码实现生产者对消息的生成&#xff0c;消费者消费消息等知识点。 希望这篇文章能帮助到…

【信创】samba的命令行使用 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;【信创】samba的命令行使用 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上使用Samba命令操作的文章。Samba是一种用于实现文件和打印共享的免费软件&#xff0c;它允许不同操作系统&#xf…

《昇思25天学习打卡营第27天》

今天我们继续Diffusion扩散模型的后半部分学习 条件U-Net 网络构建过程如下&#xff1a; 首先&#xff0c;将卷积层应用于噪声图像批上&#xff0c;并计算噪声水平的位置 接下来&#xff0c;应用一系列下采样级。每个下采样阶段由2个ResNet/ConvNeXT块 groupnorm attentio…

JAVA(IO流-字符流)day 7.30

ok了家人们今天继续学习IO流&#xff0c; 一.字符集 使用字节流输出中文可能有乱码。 因为每次读取的字节没有完全读取一个字的字节。 二.字符流 2.1 字符输出流【Writer】&#xff08;抽象类&#xff09; Writer是所有字符流的超类&#xff08;父类&#xff09; 字符输出…

蚓链数字化营销系统:“爆省”!“爆赚”!“爆值”!“爆快”!“爆增”!“爆享”!

随着信息技术的飞速发展和消费者行为的深刻变化&#xff0c;数字化营销已成为企业在市场竞争中取得优势的关键手段。蚓链数字化营销系统凭借其创新的功能和策略&#xff0c;为企业带来了一系列“爆”优势&#xff01; “按效果付费--信息化建设费用爆省”&#xff01; “按效果…

Win11没有记事本怎么办?更新至win11无法右键新建txt文件?

博主更新至Win11系统后目前用了不到一个月时间&#xff0c;今天突然发现 鼠标右键无法新建txt文件 了&#xff0c;一开始还以为Win11系统不支持txt类型文件&#xff0c;遂查找各种网上恢复教程。本文综合了多篇教程的方法&#xff0c;力求一文解决所有可能出现的情况&#xff0…

网络安全是什么?怎么入门网络安全?

一、网络安全的定义 网络安全&#xff0c;简单来说&#xff0c;就是保护网络系统中的硬件、软件以及其中的数据不因偶然或恶意的原因而遭到破坏、更改、泄露&#xff0c;保障系统连续可靠正常地运行&#xff0c;网络服务不中断。 随着信息技术的飞速发展&#xff0c;网络安全的…

JAVA基础 - 网络编程

目录 一. 网络基础 BS&#xff08;Browser/Server&#xff0c;浏览器/服务器架构&#xff09; CS&#xff08;Client/Server&#xff0c;客户端/服务器架构&#xff09; 二. TCP Socket通信 三. Socket类 四. 聊天实例 五. UDP Socket 六. 数据交换格式 一. 网络基础 网…

力反馈设备在远程机器人遥操作中的应用实例

随着科技的飞速发展&#xff0c;力反馈设备在远程机器人遥操作中的应用日益广泛&#xff0c;极大地提升了操作的精确性和安全性。其中&#xff0c;Haption Virtuose 6D力反馈设备以其卓越的性能成为该领域的佼佼者。 Haption Virtuose 6D力反馈设备医疗遥操作应用 在医疗领域&a…

公布一批脸书爬虫(facebook)IP地址,真实采集数据

一、数据来源&#xff1a; 1、这批脸书爬虫&#xff08;facebook&#xff09;IP来源于尚贤达猎头公司网站采集数据&#xff1b; ​ 2、数据采集时间段&#xff1a;2023年10月-2024年7月&#xff1b; 3、判断标准&#xff1a;主要根据用户代理是否包含“facebook”和IP核实。…

谷粒商城实战笔记-92~96-商品发布和查询

文章目录 Spu列表检索接口。Sku列表检索接口。仓库列表接口。问题记录 这一篇包含如下内容&#xff1a; 92-商品服务-API-新增商品-商品保存其他问题处理93-商品服务-API-商品管理-SPU检索94-商品服务-API-商品管理-SKU检索95-仓储服务-API-仓库管理-整合ware服务&获取仓库…

【云原生】Kubernetes中的定时任务CronJob的详细用法与企业级应用案例分享

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

皮尔逊(Person)相关系数

目录 一、总体和样本 二、总体皮尔逊相关系数 三、样本皮尔逊相关系数 四、皮尔逊相关系数的理解误区 五、总结 六、相关系数大小的解释&#xff08;并不严格&#xff09; 七、皮尔逊相关系数的计算 一、总体和样本 二、总体皮尔逊相关系数 三、样本皮尔逊相关系数 四、…

Java并发之ThreadLocal

1. 简介 ThreadLocal是 Java 中提供的一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本&#xff0c;从而实现线程隔离&#xff0c;用于解决多线程中共享对象的线程安全问题。 通常&#xff0c;我们会使用 synchronzed 关键字 或者 lock 来控制线程对临…

iPhone 手机如何查看自己的电话号码?这两种方法都可以

设置应用程序查看 第一种查看自己的电话号码的方法是在设置应用程序中的电话选项中查看当前手机的电话号码&#xff0c;下面是具体的操作步骤&#xff1a; 首先我们先打开设置应用程序&#xff0c;然后往下滑动找到电话选项&#xff0c;点击进入。 然后就可以看见界面中有“本…

【100个深度学习实战项目】目标检测、语义分割、目标追踪、图像分类等应有尽有,持续更新~~~

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 引言 本文主要介绍深度学习在各…