【Spring】循环依赖

目录标题

  • 什么是循环依赖
  • 循环依赖场景
  • Java SE 演示
  • Spring 容器演示
  • 三级缓存
    • 核心知识
      • 三级缓存
      • 四大方法
      • 三级缓存中的迁移
    • 三级缓存源码分析
    • 源码思维导图
  • 源码图例
  • 课前问题
  • 推荐阅读

  • 循环依赖是什么?
  • 循环依赖的场景有哪一些?
  • 三级缓存分别是什么?三个Map有什么异同?
  • 三级缓存是如何解决循环依赖的?
  • 为什么要使用三级缓存?为什么不可以用二级缓存?
  • 为什么构造器循环依赖、原型Bean循环依赖无法用三级缓存解决?
  • 看过 Spring源码吗?一般我们说的 Spring容器是什么?
  • 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
  • 如果循环依赖的时候,所有类又都需要 Spring AOP自动代理,那Spring如何提前曝光?曝光的是 原始bean 还是 代理后的bean ?

https://docs.spring.io/spring-framework/reference/

image.png

什么是循环依赖

多个Bean互相引用,形成环路

循环依赖场景

  • 原型Bean的循环依赖
  • 单例bean之构造注入的循环依赖
  • 单例bean之setter注入的循环依赖

前两者无法解决,最后一种可以通过Spring提供的三级缓存来进行实现。

Java SE 演示

@Component
public class ServiceA {private ServiceB serviceB;public void setServiceB(ServiceB serviceB) {this.serviceB = serviceB;System.out.println("A 里面设置了B");}// public ServiceA(ServiceB serviceB) {//     this.serviceB = serviceB;// }
}
@Component
public class ServiceB {private ServiceA serviceA;public void setServiceA(ServiceA serviceA) {this.serviceA = serviceA;System.out.println("B 里面设置了A");}// public ServiceB(ServiceA serviceA) {//     this.serviceA = serviceA;// }
}
public class ClientDemo {public static void main(String[] args) {clientSet();// clientConstruct();}/*** setter注入*/private static void clientSet() {//创建serviceAServiceA serviceA = new ServiceA();//创建serviceBServiceB serviceB = new ServiceB();//将serviceA注入到serviceB中serviceB.setServiceA(serviceA);//将serviceB注入到serviceA中serviceA.setServiceB(serviceB);}/*** 构造注入*/private static void clientConstruct(){// new ServiceA(new ServiceB(new ServiceA(new ServiceB())));}
}

Spring 容器演示

<!--    <bean id="a" class="com.example.demo.circulardependency.spring.A" scope="singleton">--><bean id="a" class="com.example.demo.circulardependency.spring.A" scope="prototype"><property name="b" ref="b"/></bean><!--    <bean id="b" class="com.example.demo.circulardependency.spring.B" scope="singleton">--><bean id="b" class="com.example.demo.circulardependency.spring.B" scope="prototype"><property name="a" ref="a"/></bean>
public class ClientSpringContainer {public static void main(String[] args) {sampleDemo();}/*** spring** 2024/2/2 11:40*/private static void sampleDemo() {/*** setter注入* * 11:39:14.055 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'* ---A created success* 11:39:14.064 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'* ---B created success*//*** 构造注入* * Exception in thread "main" org.springframework.beans.factory.BeanCreationException:**/ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");A a = context.getBean("a", A.class);B b = context.getBean("b", B.class);}
}

三级缓存

Spring循环依赖 - CSDN博客

核心知识

三级缓存

一级缓存:Map<String, Object> singletonObjects,我愿称之为成品单例池,常说的 Spring 容器就是指它,我们获取单例 bean 就是在这里面获取的,存放已经经历了完整生命周期的Bean对象
二级缓存:Map<String, Object> earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整,可以认为是 半成品的 bean, 实例化但未初始化的Bean对象
三级缓存:Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂(FactoryBean),用于生产(创建)对象

/** Cache of singleton objects: bean name to bean instance. */
// 一级缓存:singleton对象的缓存:bean名称 - bean实例。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. */
// 三级缓存:单例工厂的缓存:bean名称 - ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. */
// 二级缓存:早期singleton对象的缓存:bean名称 - bean实例。
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

四大方法

  1. getSingleton():从容器里面获得单例的bean,没有的话则会创建 bean
  2. doCreateBean():执行创建 bean 的操作(在 Spring 中以 do 开头的方法都是干实事的方法)
  3. populateBean():创建完 bean 之后,对 bean 的属性进行填充
  4. addSingleton():bean 初始化完成之后,添加到单例容器池中,下次执行 getSingleton() 方法时就能获取到

三级缓存中的迁移

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

三级缓存源码分析

【Spring】三级缓存

源码思维导图

Spring三级缓存源代码执行图

源码图例

这个地方写错了:

  • 标题应该是 初始化a 和 初始化 b。
  • 最后是 a初始化结束 和 b 初始化结束。

image.png

课前问题

在这里插入图片描述
还剩下三个:

  • 为什么不可以用二级缓存?这部分我在网上搜寻了一下,跟AOP的代理有关(由于目前我对AOP不熟,怕误导了大家,就先欠着)
  • 开发中解决循环依赖?欠着
  • 循环依赖遇上AOP?欠着

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

推荐阅读

  • Spring源码最难问题《当Spring AOP遇上循环依赖》_循环依赖aop在那个阶段-CSDN博客
  • spring 循环依赖以及解决方案(吊打面试官)_循环依赖解决方案-CSDN博客
  • spring循环依赖-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker 的安装:centos】

文章目录 1 :peach:各版本平台支持情况:peach:2 :peach:CentOS 安装:peach:2.1 :apple:安装依赖:apple:2.2 :apple:安装 Docker:apple:2.3 :apple:实战经验:apple:2.3.1 :lemon:Docker 镜像源修改:lemon:2.3.2 :lemon:Docker 目录修改:lemon: 1 &#x1f351;各版本平台支持情况…

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理 各位大佬&#xff0c;这是我的自学笔记&#xff0c;如有错误请指正&#xff0c;也欢迎在评论区学习交流&#xff0c;谢谢&#xff01; CUDA编程模型 我们使用CUDA_C语言进行CUDA编程&am…

Python爬虫之点触验证码的识别

点触验证码的识别 除了极验验证码&#xff0c;还有另一种常见且应用广泛的验证码&#xff0c;即点触验证码。 可能你对这个名字比较陌生&#xff0c;但是肯定见过类似的验证码&#xff0c;比如 12306 就是典型的点触验证码。 直接点击图中符合要求的图。所有答案均正确&#…

PLC_博图系列☞基本指令“赋值取反”

PLC_博图系列☞基本指令“赋值取反” 文章目录 PLC_博图系列☞基本指令“赋值取反”背景介绍/&#xff1a;赋值取反说明参数示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 赋值取反 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门子的博图软…

震撼升级!24版短视频矩阵全开源码,打造您的AI智能剪辑帝国

应用介绍 私信我拿演示后台 亲爱的创业者和内容创作者们&#xff0c;我们为您带来了业界颠覆性的全新产品——24版短视频矩阵全开源码。这不仅是一次技术的飞跃&#xff0c;更是您在短视频领域实现高效创作与规模化运营的一把利剑。 此次升级&#xff0c;我们彻底打破常规&…

Docker 第十九章 : 阿里云个人镜像仓使用

Docker 第十九章 : 阿里云个人镜像仓使用 本章知识点: 如何创建镜像库,如何设置密码,如何登录与退出个人镜像仓,如何本地打镜像,如何将本地镜像推送到个人镜像库。 背景 在项目YapiDocker部署中,因读取mongo:latest 版本不一致,导致后续执行步骤的异常。遇到此场景…

责任链模式与spring容器的搭配应用

背景 有个需求&#xff0c;原先只涉及到一种A情况设备的筛选&#xff0c;每次筛选会经过多个流程&#xff0c;比如先a功能&#xff0c;a功能通过再筛选b功能&#xff0c;然后再筛选c功能&#xff0c;以此类推。现在新增了另外一种B情况的筛选&#xff0c;B情况同样需要A情况的筛…

2024年机械制造业行业转型展望

行业变革中的挑战与机遇 2024年将是全球工业格局发生重大变化的一年。CADENAS着眼于最重要的五大主题&#xff1a;数字化转型、技能短缺、供应链、可持续发展和人工智能&#xff08;AI&#xff09;。这些领域为全球公司带来了挑战和机遇。 数字化转型&#xff1a;通向未来之路…

如何让电脑待机而wifi不关的操作方法!!

1、一台电脑如果一天不关机&#xff0c;大约消耗0.3度电。 一般一台电脑的功耗约为250-400W&#xff08;台式机&#xff09;。 一台电脑每月的耗电量&#xff1a;如果是每小时300W每天10小时每月30天90KW&#xff0c;即90千瓦时的电。 这只是保守估计。 2、使用完毕后正常关闭…

一次有趣的nginx Tcp4层代理转发的试验

nginx主配置文件添加配置&#xff1a; stream {log_format proxy $remote_addr [$time_local] $protocol status:$status bytes_sent:$bytes_sent bytes_received:$bytes_received $session_time upstream_addr:"$upstream_addr" "$upstream_bytes_sent" …

【Java EE初阶二十四】servlet的深入理解

1. Servlet API 的学习 下面主要学习这三个类&#xff0c;就已经可以完成 Servlet 的大部分开发了&#xff1b; 1. Httpservlet 2. HttpServletRequest 3. HttpServletResponse 2. Httpservlet的学习 2.1 Httpservlet在tomcat的工作原理 写一个 Servlet 代码&#xff0c;往往都…

代码随想录刷题39,40天|62.不同路径

62.不同路径 想要求dp[i][j]&#xff0c;只能有两个方向来推导出来&#xff0c;即dp[i - 1][j] 和 dp[i][j - 1]。 此时在回顾一下 dp[i - 1][j] 表示啥&#xff0c;是从(0, 0)的位置到(i - 1, j)有几条路径&#xff0c;dp[i][j - 1]同理。 那么很自然&#xff0c;dp[i][j] …