Spring循环依赖探究

news2024/11/17 13:31:46

1. 前言

Spring在较新版本中已经默认不允许bean之间发生「循环依赖」了,如果检测到循环依赖,容器启动时将会报错,此时可以通过配置来允许循环依赖。

spring.main.allow-circular-references=true

什么是循环依赖?
循环依赖也叫循环引用,简单点说,就是bean之间的依赖关系形成了一个循环,例如beanA依赖beanB,beanB又依赖beanA。
image.png

@Component
public class A {

    @Autowired
    B b;
}

@Component
public class A {

    @Autowired
    B b;
}

如上代码所示,Spring本身是支持循环依赖的,Spring加载bean时,首先会实例化A,然后对A做初始化,其中就包含属性填充,填充属性时发现A依赖B,于是Spring又会从容器中去加载B,创建B时发现B又依赖了A,循环依赖就此产生,Spring是如何打破这个循环的呢?

2. 依赖注入的方式

Spring完成依赖注入的方式有三种:

  1. 属性注入
  2. Setter方法注入
  3. 构造函数注入

对于构造函数注入的方式,循环依赖是无解的,Spring也无法支持。属性注入和Setter方法注入原理上是一样的,本文通过属性注入的方式来分析Spring是如何支持循环依赖的。

循环依赖仅限于单例bean,对于原型bean,循环依赖也是不允许的。

3. bean的多级缓存

Spring支持循环依赖的核心是bean的三级缓存。
Spring会在启动的时候,加载容器内所有非lazy的单例bean,方法是DefaultListableBeanFactory#preInstantiateSingletons(),该方法会把容器内所有非lazy的单例beanName拿出来,然后依次调用getBean()方法获取bean。
getBean()时,首先会调用getSingleton()方法优先从缓存中获取bean,只要缓存中存在bean,就可以直接获取了,避免二次创建。
image.png
核心就在getSingleton()方法中,Spring会在各级缓存中查找bean。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 检查一级缓存,是否存在bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // bean是否创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            /**
             * 二级缓存查找
             * 二级缓存意义不大?没有二级缓存也能解决循环依赖,作用是啥?
             * 三级缓存的ObjectFactory#getObject()方法是有代价的,会触发后置处理器获取早期引用
             * @see SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
             * 频繁触发势必带来性能问题,引入二级缓存,一旦getObject()触发就移入二级缓存
             */
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 三级缓存中查找
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

该方法做了几件事:

  1. 一级缓存中是否存在bean?存在直接返回bean。
  2. 一级缓存没有,且bean正在创建中,则去二级缓存查找。
  3. 二级缓存没有,且允许循环依赖,则去三级缓存查找。
  4. 三级缓存并没有直接存储bean,而是存储bean的工厂方法对象,通过调用工厂方法来获得bean,一旦通过三级缓存得到了bean,则将其从三级缓存移除并加入到二级缓存中。

所谓的多级缓存,其实就是三个Map容器。

/**
* 一级缓存,完整的bean容器
* 经历了完整生命周期的bean
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/**
* 二级缓存,提前暴露的bean,只实例化了,还未初始化
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/**
 * 三级缓存,不直接缓存bean
 * 而是提前暴露的bean的工厂方法,调用方法来产生bean
 * 对于需要AOP增强的bean,此时就需要生成带来对象了
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Spring解决循环依赖的思路是:当beanA还没有完全创建完毕时,就提前暴露这个半成品beanA到三级缓存,接着在创建beanB时,直接引用半成品beanA,由此来打破循环。

Spring创建A和B两个bean时,流程是这样的:

  1. getA时,一级缓存没有,且不在创建中,于是去createA。
  2. createA之前,先把A加入到singletonsCurrentlyInCreation,表示创建中。
  3. A实例化完毕,还未初始化前,先放入三级缓存singletonFactories。
  4. A填充属性时,发现需要B,去getB。
  5. getB时,一级缓存没有,且不在创建中,于是去createB。
  6. B填充属性时,发现需要A,去getA。
  7. getA发现三级缓存有,将其移入二级缓存,返回A。
  8. B创建完毕,A创建完毕。

4. 二级缓存的意义

仔细看看getSingleton()方法,会发现二级缓存好像很多余,没有二级缓存丝毫不影响循环依赖。二级缓存的作用好像就是承接了bean从三级缓存往二级缓存里迁移而已。真的是这样吗?

二级缓存还是有存在的意义的,因为只要Spring允许循环依赖,就会在创建bean的时候提前暴露到三级缓存,但是循环依赖并不一定会产生,也就是说,暴露到三级缓存里的bean其实未必会被提前引用。只要循环依赖没有产生,bean没有被提前引用,就不用触发Spring的扩展点去生成提前暴露的bean对象了。因为获取提前引用的bean对象是有代价的,bean需要被Spring的扩展点处理,如果bean需要被AOP增强,此时会提前创建代理对象。有了二级缓存,就可以保证只有在真的发生了循环依赖的情况下,才去真正的提前暴露bean对象。

Spring会在bean还没有完全创建完毕时,提前将半成品bean暴露到三级缓存中,代码在AbstractAutowireCapableBeanFactory#doCreateBean()
image.png
当Spring调用ObjectFactory#getObject()获取这个提前暴露的bean,其实会触发AbstractAutowireCapableBeanFactory#getEarlyBeanReference()

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;
}

获取提前暴露的bean引用,这个方法的调用是有代价的,它会触发容器内所有的SmartInstantiationAwareBeanPostProcessor扩展点,调用getEarlyBeanReference()方法来获取bean。如果这个bean需要被AOP增强,此时就会基于bean创建代理对象了。
也就是说,三级缓存和二级缓存其实是互斥的,只要调用了三级缓存里的ObjectFactory#getObject()方法拿到了bean,三级缓存就没用了,可以直接往二级缓存里放了。

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

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

相关文章

【Python百日进阶-数据分析】Day143 - plotly箱线图:px.box()实例

文章目录四、实例4.1 plotly.express箱线图4.1.1 基本箱线图4.1.2 为 x的每个值绘制一个箱线图4.1.3 显示基础数据4.1.4 选择计算四分位数的算法4.1.5 四分位数算法之间的区别4.1.6 风格箱线图4.1.7 Dash中的箱线图四、实例 箱线图是变量通过其四分位数分布的统计表示。盒子的…

并发编程——3.共享模型之管程

目录3.共享模型之管程3.1.共享带来的问题3.1.1.Java中的体现3.1.2.问题分析3.1.3.临界区 (Critical Section)3.1.4.竞态条件 (Race Condition)3.2.synchronized 解决方案3.3.方法上的 synchronized3.4.变量的线程安全分析3.4.1.成员变量和静态变量是否线程安全&#xff1f;3.4.…

SpringBoot+VUE前后端分离项目学习笔记 - 【07 SpringBoot实现增删改查】

增删改查代码编写 UserController.java package com.zj.demo.controller;import com.zj.demo.entity.User; import com.zj.demo.mapper.UserMapper; import com.zj.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.sp…

CVPR 2017|Deep Feature Flow for Video Recognition论文复现(pytorch版)

&#x1f3c6;引言&#xff1a;深度卷积神经网络在图像识别任务中取得了巨大的成功。然而&#xff0c;将最先进的图像识别网络转移到视频上并非易事&#xff0c;因为每帧评估速度太慢且负担不起。我们提出了一种快速准确的视频识别框架——深度特征流DFF。它只在稀疏关键帧上运…

数据结构与算法_五大算法之--回溯算法

1 回溯算法 回溯算法具有通用性&#xff0c;但是算法的效率不高&#xff0c;通常可以通过剪枝等操作提高算法的效率。 算法思想&#xff1a; 在包含问题的所有解空间树中&#xff0c;按照深度优先搜索的策略&#xff0c;从根节点出发&#xff0c;深度搜索解空间树。当搜索到某…

APSIM作物生长模型学习

由于研究需要&#xff0c;将对APSIM模型使用进行一定学习&#xff0c;特做此笔记&#xff0c;也供该模型的初学者共同进步。 首先是版本选择&#xff0c;这个模型发展较长&#xff0c;有经典的classic版本和次世代版本&#xff0c;而经过实际验证&#xff0c;次世代版本和经典版…

RHCSA 第六天笔记

网络配置 1&#xff0c;ip 命令 ip a 2,修改配置文件&#xff08;不推荐&#xff09; 3&#xff0c;nmcli命令 4&#xff0c;nmtui命令 5&#xff0c;cockpit 网络接口是指网络中的计算机或网络设备与其他设备实现通讯的进出口。这里&#xff0c;主要是指计算机的网络接口即网…

学习笔记之Vue组件化编程(二)

Vue组件化编程&#xff08;二&#xff09;Vue组件化编程一、模块与组件&#xff0c;模块化与组件化1.1 对组件的理解1.2 模块1.3 组件1.4 模块化1.5 组件化&#xff08;二&#xff09;Vue组件化编程 一、模块与组件&#xff0c;模块化与组件化 1.1 对组件的理解 在传统式编写…

Centos7下mysql8.0读写分离的配置

1.前言 1.关于读写分离的原理&#xff0c;这里不做太多赘述。主要从服务器去读取主服务的binlog日志&#xff0c;完成数据同步的过程。 这里我在mac开启了2个虚拟机&#xff0c;ip分别为192.168.31.109 ,192.168.31.208系统为centos 2.配置主从分离之前&#xff0c;需要安装…

第二十五讲:OSPF路由协议邻居认证配置

在相同OSPF区域的路由器上启用身份验证的功能&#xff0c;只有经过身份验证的同一区域的路由器才能互相通告路由信息。这样做不但可以增加网络安全性&#xff0c;对OSPF重新配置时&#xff0c;不同口令可以配置在新口令和旧口令的路由器上&#xff0c;防止它们在一个共享的公共…

android血量条的制作

最近&#xff0c;项目中需要用到血量条&#xff0c;想到血量条这东西&#xff0c;在游戏中经常见到。那么&#xff0c;再android开发中如何制作血量条呢&#xff1f;这里本人想到了两种方法&#xff0c;在网上找到一种最优方案。 方法一&#xff1a;用多张相同的图片拼凑而成的…

Docker安装nginx以及nginx-gui控制面板

一、安装nginx 1、搜索镜像 docker search nginx2、拉取镜像 docker pull nginx3、创建Nginx挂载配置文件 # 创建挂载目录 mkdir -p /install/nginx/conf mkdir -p /install/nginx/log mkdir -p /install/nginx/html# 生成容器 # 将容器nginx.conf文件复制到宿主机 # 将容器…

公网远程连接本地socket服务【内网穿透】

文章目录1. 配置本地socket服务2. 本地socket服务暴露至公网2.1 创建隧道映射9999端口2.2 获取公网地址3. 公网连接本地socket服务端1. 配置本地socket服务 Java 服务端demo环境 jdk1.8框架:springbootmaven开发工具:IDEA 在pom文件引入第三包封装的netty框架maven坐标 <…

【MyBatis】安装 + 框架搭建 + 优化 + 增删改查(全程一条龙服务讲解~)

目录 前言 一、准备工作 1.1、下载MyBatis 1.2、数据库设计 二、搭建框架 2.1、创建Maven项目 2.2、jar包、引入依赖 2.3、创建MyBatis核心配置文件 2.4、映射文件 2.5、通过junit测试功能 2.6、框架优化 三、增删改查优化 四、小结——注意事项 前言 本篇全程从0…

软件测试工程师的发展道路

最近看到一些测试朋友&#xff0c;对测试未来比较迷茫&#xff0c;不知该如何前行&#xff0c;无方向感。目前来看&#xff0c;业界目前存在一个普遍的矛盾&#xff0c;一方面很多人会觉得测试没有发展前途&#xff0c;另一方面&#xff0c;又有非常多的企业急需专业的测试人员…

React学习05-React Router 5

React Router 5 相关理解 SPA 单页Web应用&#xff08;single page web application&#xff0c;SPA&#xff09;。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面&#xff0c;只会做页面的局部更新。数据都需要通过ajax请求获取, 并在前端异步展现。 路由 什么…

全球十大数据安全事件

2021年&#xff0c;数据隐私泄露事件频发&#xff0c;涉及面广&#xff0c;影响力大&#xff0c;企业因此陷入数据保护合规与社会舆情压力的双重危机。近日&#xff0c;有国外媒体梳理了2021年十大数据泄密事件&#xff0c;并对事件进行了点评分析&#xff0c;可供读者参考。据…

第二十九讲:神州路由器DHCP协议的配置

实验拓扑图如下所示 操作步骤&#xff1a; 步骤1&#xff1a;按照图1&#xff0c;正确连接拓扑结构图。 步骤2&#xff1a;为路由器设置主机名称和配置接口IP地址。 Router>enable &#xff01;进入特权模式 Router #config &a…

MySQL高级【SQL性能分析】

目录 1&#xff1a;SQL性能分析 1.1&#xff1a;SQL执行频率 1.2&#xff1a;慢查询日志 1.3&#xff1a;profile详情 1.4&#xff1a;explain 1&#xff1a;SQL性能分析 1.1&#xff1a;SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [session|global] sta…

2022年的5G行业:“5G+”很火,5G网络迟迟未能普及

作者 | 曾响铃 文 | 响铃说 2022年&#xff0c;5G行业依旧是如火如荼地发展&#xff0c;5G技术继续深刻地改变着我们的生活与生产&#xff0c;影响社会经济发展的方方面面。 回顾过去的一年&#xff0c;5G行业有看点&#xff0c;也有疑虑。 5G网络已基本覆盖全国。截至 202…