Spring面试之循环依赖详解
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面试之循环依赖详解相关推荐
- Spring循环依赖详解
Spring循环依赖详解 什么是循环依赖 spring是如何解决循环依赖 循环源码分析 getSingletion方法 getSingleton spring开启代理对象的地方 循环依赖的限制条件 什 ...
- Spring——三级缓存解决循环依赖详解
三级缓存解决循环依赖详解 一.什么是三级缓存 二.三级缓存详解 Bean实例化前 属性赋值/注入前 初始化后 总结 三.怎么解决的循环依赖 四.不用三级缓存不行吗 五.总结 一.什么是三级缓存 就是在 ...
- Spring中循环依赖详解
目录 一.循环依赖第一种情况 一.示例代码 二.源码分析: 三.debug调试截图 二.循环依赖第二种情况--构造函数 一.示例代码: 二.运行结果: 三.为什么构造函数的循环依赖不被允许? 四.报错 ...
- Spring面试之循环依赖(allowCircularReferences)
2020-12-07日下午14:00 字节跳动面试 面试题目 2020-12-07日下午14:00 参加字节跳动在牛客网的视频面试,自我介绍完,面试官问的第一个问题:我看你的项目中使用了Spring, ...
- es6 依赖循环_require 和 import 的循环依赖详解
说到前端模块化,就不得不说到循环加载,就像混乱背后隐藏着的清晰地秩序. 什么叫循环加载? 我们来看一段代码.1 2 3 4 5 6 7 8 9 10 11 12 13const b = require ...
- 面试:讲一讲Spring中的循环依赖
前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...
- 面试必杀技,讲一讲Spring中的循环依赖
本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...
- 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖
前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...
- Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!
点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...
最新文章
- 了解C++默默编写并调用哪些函数
- JAVA基础4-循环语句
- Codeforces Round #516 (Div. 1) 题解
- Vue.js第六课 计算属性
- 请接受这份货真价“无”的PPT
- hive 自定义元数据表_[一起学Hive]之十四-Hive的元数据表结构详解
- kafka java api 删除_使用Java API创建(create),查看(describe),列举(list),删除(delete)Kafka主题(Topic)...
- android chrome cast,有了它 任何安卓设备瞬间变身ChromeCast
- RabbitMQ 普通集群配置_04
- mysql 释放行锁_《深入精通Mysql(三)》深入底层Mysql各种锁机制(面试必问)...
- Java hibernate假外键_浅谈hibernate急迫加载问题(多重外键关联)
- JS实现自动填写问卷【JS小工具】
- [吐槽]今天单纯吐槽一下VS2017社区版
- 电动自行车CE认证、电动滑板车CE认证审核新标准解析
- 【M1兼容】草图大师mac 英文版 SketchUp 2021 Mac
- windows 10安装两个eclipse
- 漫画|望京和西二旗全解读:最美互联网人
- windows如何使用bat快速安装计划任务?
- GitHub 又又又多了一个新主题 —— Dimmed Dark 主题!
- VisualSVN server下载安装