当前位置: 首页 > article >正文

系列二十、Spring循环依赖问题

一、概述

        循环依赖是指多个bean之间相互依赖,形成了一个闭环。比如A依赖于B、B依赖于C、C依赖于A,形成了一个圈,如:

二、循环依赖案例

2.1、构造方法注入产生循环依赖案例

2.1.1、ServiceA

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:14
 * @Description:
 */
@Service
public class ServiceA {

    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

2.1.2、ServiceB

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:14
 * @Description:
 */
@Service
public class ServiceB {

    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

2.1.3、MySpringConfig

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:29
 * @Description:
 */
@Configuration
@ComponentScan(basePackages = {"org.star"})
public class MySpringConfig {

}

2.1.4、AopFullAnnotationMainApp

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:14
 * @Description:
 */
@Slf4j
public class AopFullAnnotationMainApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
        ServiceA serviceA = context.getBean("serviceA", ServiceA.class);
        log.info("serviceA:{}", serviceA);
    }
}

2.1.5、结论

使用构造方法注入属性会产生循环依赖问题。

2.2、set方法注入不会产生循环依赖案例

2.2.1、ServiceAA

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:27
 * @Description:
 */
@Service
public class ServiceAA {

    @Resource
    private ServiceBB serviceBB;

}

2.2.2、ServiceBB

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:27
 * @Description:
 */
@Service
public class ServiceBB {

    @Resource
    private ServiceAA serviceAA;

}

2.2.3、MySpringConfig(同上)

2.2.4、AopFullAnnotationMainApp

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:14
 * @Description:
 */
@Slf4j
public class AopFullAnnotationMainApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
        ServiceAA serviceAA = context.getBean(ServiceAA.class);
        log.info("serviceAA:{}", serviceAA);
    }
}

2.2.5、结论

使用set方法注入属性不会产生循环依赖问题。

2.2.6、注意事项

        默认情况下使用set方法注入属性可以解决循环依赖问题,但是有一个前提,bean是单例的,当把bean设置为多例后,再使用set注入时依然会产生循环依赖问题。

三、Spring解决循环依赖的方法

3.1、三级缓存

        所谓Spring的三级缓存是指Spring内部解决循环依赖问题的三个Map,即 DefaultSingletonBeanRegistry 中定义的三个Map。部分源码如下:

3.2、A、B对象在三级缓存中的迁移过程

第一步:A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B;

第二步:B实例化的时候发现需要A,于是B先查找一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,于是找到A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A;

第三步:B顺利完成初始化,并将自己放到一级缓存里面(注意:此时B里面的A依然是创建中的状态),然后接着回来创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

3.3、断点顺序

# 参考B站尚硅谷周阳老师录制的视频,链接地址如下
 
https://www.bilibili.com/video/BV1Hy4y1B78T?p=39&spm_id_from=pageDriver&vd_source=72ebc194dbfa51ec950a52c25c337f7c

 

3.4、解决循环依赖的步骤

        第一步:Spring创建bean主要分为两个步骤,即:创建原始bean对象 & 填充对象属性和初始化;

        第二步:每次创建bean之前,Spring都会从缓存中查询该bean是否存在,如果缓存中存在,则直接拿来用,如果没有Spring则去创建原始对象并放入三级缓存中,因为是单例,只能有一个;

        第三步:当创建完A的原始对象后(此时A已经在三级缓存中了),接下来为A填充属性,这时候发现A依赖B,接着又去创建B,同样的流程,创建完B的原始对象后,填充属性时发现B又依赖于A,B实例化的时候需要A,于是B先从一级缓存里面查找A,没有找到,接着再查二级缓存,还是没有,再查三级缓存,于是找到了A,然后Spring把三级缓存里面的A放到二级缓存里面,并删除三级缓存里面的A,B顺利完成初始化,并将自己放入到一级缓存里面(注意:此时B里面的属性A依然是创建中的状态),然后接着回来继续为A填充属性,此时B已经创建结束,Spring直接从一级缓存中取出B,为A赋值,完成A的初始化,然后把A自己放入到一级缓存里面,并删除二级缓存里面的A,此时A,B各自完成实例化。

3.5、解决循环依赖的思想

        Spring解决循环依赖靠的是bean的 中间态 这个概念,而这个中间态指的是 已经实例化但是还没有初始化的状态,即:半成品;实例化的过程又是通过构造器完成的,如果A还没有创建出来怎么提前曝光?这也就解释了使用构造方法注入属性为什么无法解决循环依赖的问题。

Spring解决单例bean循环依赖的三个Map(三级缓存):

        # 一级缓存:单例池,存放已经经历了完整生命周期的bean

        private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

        # 二级缓存:存放早期暴露出来的bean对象,此时bean的生命周期尚未结束(属性还未填充完毕)

        private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

        # 三级缓存:存放可以生成bean的工厂

        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

3.6、详细过程


http://www.kler.cn/a/146173.html

相关文章:

  • 【论文笔记】SmileSplat:稀疏视角+pose-free+泛化
  • macOS Sequoia 15.3 beta3(24D5055b)发布,附黑、白苹果镜像下载地址
  • 如何在linux系统上完成定时开机和更新github端口的任务
  • Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
  • 基于.Net Core+Vue的文件加密系统
  • NodeJS | 搭建本地/公网服务器 live-server 的使用与安装
  • 【Web】[GKCTF 2021]easycms
  • 国产1433D/E/F/H手持式信号发生器,可覆盖到50GHz
  • glibc和gcc源码
  • COMP2121 Discrete Mathematics
  • Simulink 模型简单加密
  • Python开发运维:Celery连接Redis
  • Python中的sys模块详解
  • 人力资源管理后台 === 左树右表
  • pytest调用其他测试用例方法
  • Linux常用命令——bc命令
  • spring-webmvc练习-日程管理-访问后端展示列表数据
  • 一个基于.NET Core开源、跨平台的仓储管理系统
  • 当「华为还是备选,迪爹还是迪子」时宇宙厂一面原题
  • 【技巧】前端开发技巧 增加前端的请求缓存 提高开发效率
  • Stable-Diffusion——Windows部署教程
  • 【Ambari】HDP单机自动化安装(基础环境和MySQL脚本一键安装)
  • 【ARM 嵌入式 编译 Makefile 系列 18 -- Makefile 中的 export 命令详细介绍】
  • 常用脚本-持续更新(文件重命名、视频抽帧、拆帧、删除冗余文件、yolo2xml、转换图片格式、修改xml)
  • ESXi 添加虚拟闪存 无可选设备问题排查
  • 优秀的时间追踪软件Timemator for Mac轻松管理时间!