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. 发布 vscode 插件 Cnblogs Client For VSCode 预览版

    为了方便大家使用 vscode 发布博文,我们做了一个小插件,今天发布预览版,欢迎大家试用并反馈问题与建议. 插件的英文名称是 Cnblogs Client For VSCode,简称是 vscode ...

  2. MYSQL架构理解

    目录 一.MYSQL架构 1. 架构图 2.分层实现 3.查询组件 二.并发控制 三. 事务 四.引擎 摘自 通过对MYSQL重要的几个属性的理解,建立一个基本的MYSQL的知识框架 一.MYSQL架 ...

  3. Jenkins_创建任务以及定时启动(2)

    一.创建任务 1.点击New Item 2.输入用户,单击Freestyle project,点击OK 3.填写构建步骤,因为是安装在linux上的,所以我们选择Execute shell,随意输入一 ...

  4. react中使用styled-component

    styled-component的使用地址(https://www.cnblogs.com/aichenxy/p/8672752.html)

  5. 接口神器之 Json Server 详细指南

    简介 json-server 是一款小巧的接口模拟工具,一分钟内就能搭建一套 Restful 风格的 api,尤其适合前端接口测试使用. 只需指定一个 json 文件作为 api 的数据源即可,使用起 ...

  6. Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发

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

  7. 洛谷 CF196A 题解

    题目传送门 题目描述: 读入字符串,求该串的最大字典序子序列. 我的思路: 循环判断只要当前字符比后面所有的字符的字典序大,就把这个字符存到另一个字符串中,最后和输入的字符串循环比较如果该字符和输入时 ...

  8. java 报错: MalformedURLException: unknow protocol: f

    问题: java 使用 URL 读取文件解读报错 MalformedURLException 1.使用 URL对象解析文件报错MalformedURLException: unknow protoco ...

  9. 《剑指offer》面试题68 - I. 二叉搜索树的最近公共祖先

    问题描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x 是 p ...

  10. porcupine语音唤醒python实现

    note it is not for arm pyaudio <= 3.6 version porcupine 3.5 3.6 not 3.7 code import struct import ...