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 ...
随机推荐
- .net core集成使用EasyNetQ来使用rabbitmq
之前有写到一篇介绍EasyNetQ的博文(C# .net 使用rabbitmq消息队列--EasyNetQ插件介绍),所以本文从.net core的角度去继承使用EasyNetQ,而用法类似于之前集成 ...
- 集合不安全之 ArrayList及其三种解决方案【CopyOnWriteArrayList 、synchronizedList、Vector 】
@ 目录 一.前言 二.为什么线程不安全 三.解决方案一CopyOnWriteArrayList (推荐,读多写少场景) 四.Collections.synchronizedList(加锁) 五.Ve ...
- CAP原理详解
转载 https://blog.csdn.net/u013332124/article/details/82874178 文章目录 一.CAP原理介绍 对CAP原理的一些常见的理解误区 二.CAP原理 ...
- spring boot + spring security +JWT令牌 +前后端分离--- 心得
1.前言 观看这篇随笔需要有spring security基础. 心得: 1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定 2.生成的token是将登录通过的用户的权限拼接的字符 ...
- CentOS6.5安装CM5.13
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6610650059971232269/ 安装好虚拟机(2个节点) YUM源已安装好.系统版本CentOS6.5.ja ...
- vue js 格式化时间
常用于iview日期组件时间处理 /** * @param {Number} num 数值 * @returns {String} 处理后的字符串 * @description 如果传入的数值小于10 ...
- css画叉叉(一般用于关闭按钮)
css 一般用于右上角关闭弹窗 #pdclose { width: 18px; height: 18px; cursor: pointer; float: right; position: relat ...
- 学习笔记--Java中的变量
Java中的变量 /** * 关于 Java 语言当中的变量: * * 1. 什么是变量? * - 变量的本质上来说是内存空间,这块空间有(数据类型.名字.字面值) * - 变量包括三部分:数据类型. ...
- 网络协议学习笔记(五)套接字Socket
概述 前面学习网络知识的时候写过一篇关于套接字的随笔见<JAVA SOCKET 详解>,现在本人正在系统的学习网络知识,现在除了温故知新之外,在详细的学习记录一下套接字的知识. Socke ...
- 【经验总结】CodeBlocks使用mingw64
CodeBlocks使用 标签:c++ 一.安装并配置mingw-w64 使用中发现CB默认的编译器版本过低,c++11的一些东西无法使用,比如string中的stoi函数,因此尝试安装新版本的编译器 ...