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. .net core的配置介绍(一):IConfiguration

    说到配置,绝大部分系统都会有配置,不需要配置的系统是非常少的,想想以前做.net 开发时,我们常常将配置放到web.config中,然后使用ConfigurationManager去读取. 初次接触到 ...

  2. 图解MySQL:count(*) 、count(1) 、count(主键字段)、count(字段)哪个性能最好?

    大家好,我是小林. 当我们对一张数据表中的记录进行统计的时候,习惯都会使用 count 函数来统计,但是 count 函数传入的参数有很多种,比如 count(1).count(*).count(字段 ...

  3. css 基础 css引入方式

    color:red; //改变颜色 font-size:18px : //改变文字大小 background-color : blue; //设置背景颜色 width:100px ://设置宽度 he ...

  4. CentOS 7 把已登录的用户断开

    1. 查看登陆用户 [root@localhost ~]#  w 18:29:30 up 377 days, 8:44, 4 users, load average: 0.05, 0.12, 0.09 ...

  5. 怎样在 CentOS/RHEL 7/6 上安装和配置 Sendmail 服务器

    在 CentOS 上,可以通过 mailx 命令 或 sendmail 命令来给因特网发送电子邮件. 关于前者的文章比较多,关于后者的文章比较少. 这里记录了我在 CentOS 7 上安装和配置 se ...

  6. Go语言系列之自定义实现日志库

    日志库logo gitee地址传送门:https://gitee.com/zhangyafeii/logo 日志库需求分析 1. 支持往不同的地方输出日志 2. 日志分级别 Debug Trace I ...

  7. 修正了Model1模式,进入如今盛行的的Model2模式,也就是MVC模式

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6513668601843548675/ 1.<JSP页面实际上就是Servlet> 2.<JSP页 ...

  8. winform 中心旋转 图片旋转

    //设置左上角到中心点 g.TranslateTransform(int.Parse(x), int.Parse(y)); //旋转角度 g.RotateTransform(int.Parse(&qu ...

  9. 毫米转像素dpi

    public static double MillimeterToPixel_X(double length) //length是毫米,1厘米=10毫米 { System.Windows.Forms. ...

  10. eclipse导入项目jdk版本不一样

    一:eclipse导入项目jdk版本不一样解决方案 参考博文: https://www.cnblogs.com/chenmingjun/p/8472885.html 选中项目右键 --> Pro ...