1.什么是循环依赖?

多个bean之间相互依赖,形成了一个闭环。比如:A依赖于B、B依赖于C、C依赖于A。
通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。

2.两种注入方式对循环依赖的影响

构造方法注入可能会造成循环依赖

我们A,B循环依赖问题只要A的注入方式是setter且singleton ,就不会有循环依赖问题,spring底层会解决循环依赖问题。

3.javaSE代码演示
话不多说,上代码
3.1构造器方式注入依赖(不可行)

/*** @author LongXi* @create 2021-06-03 20:21*/
@Component
public class ServiceB {private ServiceA serviceA;public ServiceB(ServiceA serviceA){this.serviceA = serviceA;}
}
/*** @author LongXi* @create 2021-06-03 20:20*/
@Component
public class ServiceA{private ServiceB serviceB;public ServiceA(ServiceB serviceB){this.serviceB = serviceB;}
}

测试类

/*** @author LongXi* @create 2021-06-03 20:23*/
public class MyTest {@Testpublic void test(){new ServiceA(new ServiceB(new ServiceA()));//这会抛出编译异常}
}

直接报错

3.2以set方式注入依赖(可行)

/*** @author LongXi* @create 2021-06-03 20:28*/
public class ServiceAA {private ServiceBB serviceBB;public void setServiceBB(ServiceBB serviceBB){this.serviceBB = serviceBB;System.out.println("A里面设置了B");}
}
/*** @author LongXi* @create 2021-06-03 20:29*/
public class ServiceBB {private ServiceAA serviceAA;public void setServiceAA(ServiceAA serviceAA){this.serviceAA = serviceAA;System.out.println("B里面设置了A");}
}

测试类

/*** @author LongXi* @create 2021-06-03 20:23*/
public class MyTest {@Testpublic void test(){//new ServiceA(new ServiceB(new ServiceA()));//这会抛出编译异常//创建serviceAAServiceAA a = new ServiceAA();//创建serviceBBServiceBB b = new ServiceBB();//将serviceA入到serviceB中b.setServiceAA(a);//将serviceB法入到serviceA中a.setServiceBB(b);}
}

结果

B里面设置了A
A里面设置了BProcess finished with exit code 0

3.3 spring循环依赖BUG演示

/*** @author LongXi* @create 2021-06-03 20:39*/
public class A {private B b;public B getB() {return b;}public void setB(B b) {this.b = b;System.out.println("A call setB.");}
}
/*** @author LongXi* @create 2021-06-03 20:39*/
public class B {private A a;public A getA() {return a;}public void setA(A a) {this.a = a;System.out.println("B call setA.");}
}

配置文件
默认的单例(Singleton)的场景是支持循环依赖的,不报错

    <bean id="a" class="com.lx.dao.A"><property name="b" ref="b"></property></bean><bean id="b" class="com.lx.dao.B"><property name="a" ref="a"></property></bean>

测试类

    @Testpublic void Test2(){ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");A a = context.getBean("a", A.class);B b = context.getBean("b", B.class);}

测试结果

B call setA.
A call setB.Process finished with exit code 0

原型(Prototype)的场景是不支持循环依赖的,会报错

    <bean id="a" class="com.lx.dao.A" scope="prototype"><property name="b" ref="b" ></property></bean><bean id="b" class="com.lx.dao.B" scope="prototype"><property name="a" ref="a"></property></bean>

测试类不变,结果报错

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'b' while setting bean property 'b'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'a' while setting bean property 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:314)at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1681)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1433)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:338)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1111)at MyTest.Test2(MyTest.java:29)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'a' while setting bean property 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

这就是bean循环依赖的错误。

解决方法:
重要结论(spring内部通过3级缓存来解决循环依赖) - DefaultSingletonBeanRegistry

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题。
而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建。
所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象。(ConcurrentHashMap)

第二级缓存:earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完。)

第三级缓存:Map<String, ObjectFactory<?>> singletonFactories,存放可以生成Bean的工厂

底层代码:

package org.springframework.beans.factory.support;...public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {.../** Cache of singleton objects: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. */private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);...}

4.spring循环依赖debug前置知识

实例化 - 内存中申请一块内存空间,如同租赁好房子,自己的家当还未搬来。

初始化属性填充 - 完成属性的各种赋值,如同装修,家具,家电进场。


第一层:singletonObjects存放的是已经初始化好了的Bean,

第二层:earlySingletonObjects存放的是实例化了,但是未初始化的Bean,

第三层:singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

package org.springframework.beans.factory.support;...public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {.../** 单例对象的缓存:bean名称—bean实例,即:所谓的单例池。表示已经经历了完整生命周期的Bean对象第一级缓存*/private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/**早期的单例对象的高速缓存: bean名称—bean实例。表示 Bean的生命周期还没走完(Bean的属性还未填充)就把这个 Bean存入该缓存中也就是实例化但未初始化的 bean放入该缓存里第二级缓存*/private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);/**单例工厂的高速缓存:bean名称—ObjectFactory表示存放生成 bean的工厂第三级缓存*/private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);...}
@FunctionalInterface
public interface ObjectFactory<T> {T getObject() throws BeansException;}

A / B两对象在三级缓存中的迁移说明

1.A创建过程中需要B,于是A将自己放到三级缓里面,去实例化B。

2.B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A。
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

3.B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

5.spring循环依赖debug源码

源码太多,自行debug

6.总结
Spring解决循环依赖依靠的是Bean的"中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态—>半成品。实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循坏依赖问题,使用了三级缓存:

其中一级缓存为单例池(singletonObjects)。

二级缓存为提前曝光对象(earlySingletonObjects)。

三级级存为提前曝光对象工厂(singletonFactories) 。

假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A。
没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化。
这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

Spring解决循环依赖过程:

1.调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA

2.在getSingleton()方法中,从一级缓存中查找,没有,返回null

3.doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)

4.在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法

5.进入AbstractAutowireCapableBeanFactory#ndoCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中

6.对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB

7.调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性

8.此时 beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA

9.这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中

10.随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

Spring面试之循环依赖详解相关推荐

  1. Spring循环依赖详解

    Spring循环依赖详解 什么是循环依赖 spring是如何解决循环依赖 循环源码分析 getSingletion方法 getSingleton spring开启代理对象的地方 循环依赖的限制条件 什 ...

  2. Spring——三级缓存解决循环依赖详解

    三级缓存解决循环依赖详解 一.什么是三级缓存 二.三级缓存详解 Bean实例化前 属性赋值/注入前 初始化后 总结 三.怎么解决的循环依赖 四.不用三级缓存不行吗 五.总结 一.什么是三级缓存 就是在 ...

  3. Spring中循环依赖详解

    目录 一.循环依赖第一种情况 一.示例代码 二.源码分析: 三.debug调试截图 二.循环依赖第二种情况--构造函数 一.示例代码: 二.运行结果: 三.为什么构造函数的循环依赖不被允许? 四.报错 ...

  4. Spring面试之循环依赖(allowCircularReferences)

    2020-12-07日下午14:00 字节跳动面试 面试题目 2020-12-07日下午14:00 参加字节跳动在牛客网的视频面试,自我介绍完,面试官问的第一个问题:我看你的项目中使用了Spring, ...

  5. es6 依赖循环_require 和 import 的循环依赖详解

    说到前端模块化,就不得不说到循环加载,就像混乱背后隐藏着的清晰地秩序. 什么叫循环加载? 我们来看一段代码.1 2 3 4 5 6 7 8 9 10 11 12 13const b = require ...

  6. 面试:讲一讲Spring中的循环依赖

    前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...

  7. 面试必杀技,讲一讲Spring中的循环依赖

    本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...

  8. 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖

    前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...

  9. Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!

    点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...

最新文章

  1. 了解C++默默编写并调用哪些函数
  2. JAVA基础4-循环语句
  3. Codeforces Round #516 (Div. 1) 题解
  4. Vue.js第六课 计算属性
  5. 请接受这份货真价“无”的PPT
  6. hive 自定义元数据表_[一起学Hive]之十四-Hive的元数据表结构详解
  7. kafka java api 删除_使用Java API创建(create),查看(describe),列举(list),删除(delete)Kafka主题(Topic)...
  8. android chrome cast,有了它 任何安卓设备瞬间变身ChromeCast
  9. RabbitMQ 普通集群配置_04
  10. mysql 释放行锁_《深入精通Mysql(三)》深入底层Mysql各种锁机制(面试必问)...
  11. Java hibernate假外键_浅谈hibernate急迫加载问题(多重外键关联)
  12. JS实现自动填写问卷【JS小工具】
  13. [吐槽]今天单纯吐槽一下VS2017社区版
  14. 电动自行车CE认证、电动滑板车CE认证审核新标准解析
  15. 【M1兼容】草图大师mac 英文版 SketchUp 2021 Mac
  16. windows 10安装两个eclipse
  17. 漫画|望京和西二旗全解读:最美互联网人
  18. windows如何使用bat快速安装计划任务?
  19. GitHub 又又又多了一个新主题 —— Dimmed Dark 主题!
  20. VisualSVN server下载安装

热门文章

  1. 游戏出海新机遇 :卓杭游戏携手云开发和云函数,打造小游戏出海新爆款
  2. 谈谈CListCtrl 扩展风格设置方法-SetExtendedStyle和ModifyStyleEx 比较
  3. Qtum量子链Github 开发指南
  4. 江苏省计算机高考试题,08-16江苏省单招计算机网络高考试题汇总.doc
  5. 赖世雄教你学英语语法学习笔记(未完)
  6. From GMM to GrabCut
  7. HTML图片在div中心旋转+变色 鼠标移除后 慢慢恢复原样
  8. 中国计算机学会推荐国际学术刊物与国际会议
  9. 项目预算和核算管理过程文件
  10. Apache Karaf离线打包