Spring 是怎么处理循环依赖的?
Java语法中的循环依赖
首先看一个使用构造函数的循环依赖,如下:
public class ObjectA {
private ObjectB b;
public ObjectA(ObjectB b) {
this.b = b;
}
}
public class ObjectB {
private ObjectA a;
public ObjectB(ObjectA a) {
this.a = a;
}
}
public class Main {
public static void main(String[] args) {
//ObjectB b = new ObjectB(new ObjectA(new ObjectB()));
}
}
大家可以看上面这个例子,可以看出是没有办法new出ObjectA或者ObjectB的
那怎么解决上面的例子呢?如下:
public class ObjectA {
private ObjectB b;
public void setB(ObjectB b) {
this.b = b;
}
public ObjectB getB() {
return this.b;
}
}
public class ObjectB {
private ObjectA a;
public void setA(ObjectA a) {
this.a = a;
}
public ObjectA getA() {
return this.a;
}
}
public class Main {
public static void main(String[] args) {
ObjectB b = new ObjectB();
ObjectA a = new ObjectA();
b.setA(a);
a.setB(b);
System.out.println(a + " " + a.getB());
System.out.println(b + " " + b.getA());
}
}
输出如下:
cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460
cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d
可以看出把构造函数
去掉,然后增加set方法
就可以实现循环依赖的问题了。
Spring 的构造函数循环依赖
测试例子如下:
@Component
public class MyBeanOne {
private MyBeanTwo myBeanTwo;
@Autowired
public MyBeanOne(MyBeanTwo myBeanTwo) {
this.myBeanTwo = myBeanTwo;
}
}
@Component
public class MyBeanTwo {
private MyBeanOne myBeanOne;
@Autowired
public MyBeanTwo(MyBeanOne myBeanOne) {
this.myBeanOne = myBeanOne;
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);
MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);
MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);
System.out.println(myBeanOne);
System.out.println(myBeanTwo);
}
}
输出如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'myBeanOne': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
在DefaultSingletonBeanRegistry.beforeSingletonCreation()
里打断点,看一下为什么出错了:
第一次经过:
第二次经过:
第三次经过:
我们可以猜测出错的原因是这样的:
去生成myBeanOne,需要生成myBeanTwo
去生成myBeanTwo,需要生成myBeanOne
去生成myBeanOne,发现myBeanOne已经在创建中了
Spring 的Set方式循环依赖
@Component
public class MyBeanOne {
@Autowired
@Getter
private MyBeanTwo myBeanTwo;
}
@Component
public class MyBeanTwo {
@Autowired
@Getter
private MyBeanOne myBeanOne;
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);
MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);
MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);
System.out.println(myBeanOne + " " + myBeanOne.getMyBeanTwo());
System.out.println(myBeanTwo + " " + myBeanTwo.getMyBeanOne());
}
}
输出:
cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114 cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869
cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114
可以看出通过set注入的方式是可以解决循环依赖问题的。
我们继续在DefaultSingletonBeanRegistry.beforeSingletonCreation()
里打断点,看一下set方式是怎么经过这里的:
发现只经过了两次,没有第三次,如果有第三次的话,也就抛异常了,所以只经过两次是正常的。
为什么构造函数有三次,而set方式有两次?
我们看一下DefaultSingletonBeanRegistry.beforeSingletonCreation
的调用链:
AbstractBeanFactory.doGetBean
DefaultSingletonBeanRegistry.getSingleton
DefaultSingletonBeanRegistry.beforeSingletonCreation
那我们就在AbstractBeanFactory.doGetBean
这里打断点,看一下set方式,为什么没有第三次
第一次:
第二次:
第三次:
我们可以看到第三次的myBeanOne
已经有值了,它就不会执行到DefaultSingletonBeanRegistry.beforeSingletonCreation
如果换成构造方式来调试的话,在第三次,myBeanOne
依旧是null
的,就会继续往下执行到DefaultSingletonBeanRegistry.beforeSingletonCreation
,然后就会抛错。
我们来看一下第三次的myBeanOne
是怎么获取的:
DefaultSingletonBeanRegistry.getSingleton
方法如下:
可以看到是通过this.singletonFactories.get(beanName)
得到一个工厂,通过这个工厂可以创建出对应的bean
我们再来看一下这些个工厂是什么时候被放进去的,DefaultSingletonBeanRegistry.addSingletonFactory
:
为什么通过构造函数注入的方式,没有提前放入一个工厂
再执行AbstractAutowireCapableBeanFactory.createBeanInstance
方法时
set方式会执行以下:
调用这个方法之后,回退到 AbstractAutowireCapableBeanFactory.doCreateBean()
继续往下执行,会把工厂放进去。
构造函数的方式会执行以下:
调用这个方法,后面会继续获取myBeanTwo
,如下:
然后同理再获取myBeanOne
,就会抛异常了,它不会回退到 AbstractAutowireCapableBeanFactory.doCreateBean()
,自然也不会把工厂放进去。
最后理一下
对于Set方式,当类构造好之后,会提前把生成这个类的工厂放到缓存中;而构造函数的方式,由于存在构造函数,必须在当下去获取依赖类,所以就没办法构造类,其实原理和刚开始举的Java的例子是一个道理。
参考
Spring 解决循环依赖必须要三级缓存吗?
Spring循环依赖三级缓存是否可以去掉第三级缓存?
Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗
Spring 是怎么处理循环依赖的?的更多相关文章
- [跟我学spring学习笔记][DI循环依赖]
循环依赖 什么是循环依赖? 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方. Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢? ...
- Spring.getBean()流程和循环依赖的解决
getBean流程介绍(以单例的Bean流程为准) getBean(beanName) 从BeanFactory中获取Bean的实例对象,真正获取的逻辑由doGetBean实现. doGetBean( ...
- Spring是如何解决循环依赖的
前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过s ...
- spring: 我是如何解决循环依赖的?
1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...
- Spring 是如何解决循环依赖的?
前言 相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的: 还会提示这么一句: Requested bean is currently in creation: Is there an ...
- 听说你还不知道Spring是如何解决循环依赖问题的?
Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...
- Spring源码解析——循环依赖的解决方案
一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...
- Spring 使用@Async出现循环依赖Bean报错的解决方案
初现端倪 Caused by:org.springframework.beans.factory.BeanCurrentlyInCreationException: Errorcreating bea ...
- 从源码解读Spring如何解决bean循环依赖
1 什么是bean的循环依赖 循环依赖的原文是circular reference,指多个对象相互引用,形成一个闭环. 以两个对象的循环依赖为例: Spring中的循环依赖有 3 种情况: 构造器(c ...
随机推荐
- 发布 vscode 插件 Cnblogs Client For VSCode 预览版
为了方便大家使用 vscode 发布博文,我们做了一个小插件,今天发布预览版,欢迎大家试用并反馈问题与建议. 插件的英文名称是 Cnblogs Client For VSCode,简称是 vscode ...
- MYSQL架构理解
目录 一.MYSQL架构 1. 架构图 2.分层实现 3.查询组件 二.并发控制 三. 事务 四.引擎 摘自 通过对MYSQL重要的几个属性的理解,建立一个基本的MYSQL的知识框架 一.MYSQL架 ...
- Jenkins_创建任务以及定时启动(2)
一.创建任务 1.点击New Item 2.输入用户,单击Freestyle project,点击OK 3.填写构建步骤,因为是安装在linux上的,所以我们选择Execute shell,随意输入一 ...
- react中使用styled-component
styled-component的使用地址(https://www.cnblogs.com/aichenxy/p/8672752.html)
- 接口神器之 Json Server 详细指南
简介 json-server 是一款小巧的接口模拟工具,一分钟内就能搭建一套 Restful 风格的 api,尤其适合前端接口测试使用. 只需指定一个 json 文件作为 api 的数据源即可,使用起 ...
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- 洛谷 CF196A 题解
题目传送门 题目描述: 读入字符串,求该串的最大字典序子序列. 我的思路: 循环判断只要当前字符比后面所有的字符的字典序大,就把这个字符存到另一个字符串中,最后和输入的字符串循环比较如果该字符和输入时 ...
- java 报错: MalformedURLException: unknow protocol: f
问题: java 使用 URL 读取文件解读报错 MalformedURLException 1.使用 URL对象解析文件报错MalformedURLException: unknow protoco ...
- 《剑指offer》面试题68 - I. 二叉搜索树的最近公共祖先
问题描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x 是 p ...
- porcupine语音唤醒python实现
note it is not for arm pyaudio <= 3.6 version porcupine 3.5 3.6 not 3.7 code import struct import ...