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 是怎么处理循环依赖的?的更多相关文章

  1. [跟我学spring学习笔记][DI循环依赖]

    循环依赖 什么是循环依赖? 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方. Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢? ...

  2. Spring.getBean()流程和循环依赖的解决

    getBean流程介绍(以单例的Bean流程为准) getBean(beanName) 从BeanFactory中获取Bean的实例对象,真正获取的逻辑由doGetBean实现. doGetBean( ...

  3. Spring是如何解决循环依赖的

    前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过s ...

  4. spring: 我是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  5. Spring 是如何解决循环依赖的?

    前言 相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的: 还会提示这么一句: Requested bean is currently in creation: Is there an ...

  6. 听说你还不知道Spring是如何解决循环依赖问题的?

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

  7. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  8. Spring 使用@Async出现循环依赖Bean报错的解决方案

    初现端倪 Caused by:org.springframework.beans.factory.BeanCurrentlyInCreationException: Errorcreating bea ...

  9. 从源码解读Spring如何解决bean循环依赖

    1 什么是bean的循环依赖 循环依赖的原文是circular reference,指多个对象相互引用,形成一个闭环. 以两个对象的循环依赖为例: Spring中的循环依赖有 3 种情况: 构造器(c ...

随机推荐

  1. 为什么要避免在 Go 中使用 ioutil.ReadAll?

    原文链接: 为什么要避免在 Go 中使用 ioutil.ReadAll? ioutil.ReadAll 主要的作用是从一个 io.Reader 中读取所有数据,直到结尾. 在 GitHub 上搜索 i ...

  2. 【Python+Django+Pytest】数据库异常pymysql.err.InterfaceError: (0, '') 解决方案

    问题背景: 接口自动化测试平台,在执行测试案例之外,还需要做以下五件事情(或步骤): 1.查询用户在数据准备中预置的测试套件层数据初始化相关sql  (setUp_class方法中) 2.查询用户在数 ...

  3. centos7 安装zabbix3.0 安装zabbix4.0 yum安装zabbix 国内源安装zabbix 阿里云服务器安装zabbix

    首先,此篇文章是有原因的. 刚开始也和大家一样来学习安装zabbix 奈何网上的教程和现实出现不一样的情况 在安装zabbix过程中,因为zabbix下载源是在国外,下载途中会出现终止下载的情况 tr ...

  4. 实验 1 :Mininet 源码安装和可视化拓扑

    实验 1 : Mininet 源码安装和可视化拓扑工具 一 .实验目的 掌握 Mininet 的源码安装方法和 miniedit 可视化拓扑生成工具. 二 .实验任务 使用源码安装 Mininet 的 ...

  5. Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. MCU软件最佳实践——独立按键

    1. 引子 在进行mcu驱动和应用开发时,经常会遇到独立按键驱动的开发,独立按键似乎是每一个嵌入式工程师的入门必修课.笔者翻阅了许多书籍(包括上大学时候用的书籍)同时查阅了网上许多网友的博客,无一例外 ...

  7. day 20 C语言顺序结构基础3

    (1).若有定义:int a=100:则语句printf("%d%d%d\n",sizeof("a"),sizeof(a),sizeof(3.14)); 则输出 ...

  8. 学习javaScript必知必会(3)~数组(数组创建,for...in遍历,辅助函数,高级函数filter、map、reduce)

    一.数组: 1.js是弱语言,js中的数组定义时:不用指定数据类型.不用功指定数组长度:数组可以存储任何数据类型的数据 2.数组定义的[ ] 的实质: [] = new Array(); {} = n ...

  9. JAVA实现对阿里云DNS的解析管理

    1.阿里云DNS的SDK依赖 <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-op ...

  10. 为什么JavaWeb要分层

    首先bai让我们坐着时光机回到n年前的web开发.那个时候最早du都是静态的html页面,zhi后来有了数据库,有了所谓dao的动态页面,然后程序猿在编码的时候,会把所有的代码都写在页面上,包括数据库 ...