【spring】循环依赖 Java Vs Spring
菜瓜:水稻,这次我特意去看了java的循环依赖
水稻:哟,有什么收获
菜瓜:两种情况,构造器循环依赖,属性循环依赖
- 构造器循环依赖在逻辑层面无法通过。对象通过构造函数创建时如果需要创建另一个对象,就会存在递归调用。栈内存直接溢出
- 属性循环依赖可以解决。在对象创建完成之后通过属性赋值操作。
package club.interview.base; /**
* 构造器循环依赖 - Exception in thread "main" java.lang.StackOverflowError
* toString()循环打印也会异常 - Exception in thread "main" java.lang.StackOverflowError
* @author QuCheng on 2020/6/18.
*/
public class Circular { class A {
B b; // public A() {
// b = new B();
// } // @Override
// public String toString() {
// return "A{" +
// "b=" + b +
// '}';
// }
} class B {
A a; // public B() {
// a = new A();
// } // @Override
// public String toString() {
// return "B{" +
// "a=" + a +
// '}';
// }
} private void test() {
B b = new B();
A a = new A();
a.b = b;
b.a = a;
System.out.println(a);
System.out.println(b);
} public static void main(String[] args) {
new Circular().test();
}
}
水稻:厉害啊,Spring也不支持构造函数的依赖注入,而且也不支持多例的循环依赖。同样的,它支持属性的依赖注入。
- 看效果 - 如果toString()打印同样会出现栈内存溢出。
package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/18.
*/
@Component("a")
public class CircularA { @Resource
private CircularB circularB; // @Override
// public String toString() {
// return "CircularA{" +
// "circularB=" + circularB +
// '}';
// }
} package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/18.
*/
@Component("b")
public class CircularB { @Resource
private CircularA circularA; // @Override
// public String toString() {
// return "CircularB{" +
// "circularA=" + circularA +
// '}';
// }
} @Test
public void testCircular() {
String basePackages = "com.vip.qc.circular";
new AnnotationConfigApplicationContext(basePackages);
}
菜瓜:看来spring的实现应该也是通过属性注入的吧
水稻:你说的对。先给思路和demo,之后带你扫一遍源码,follow me !
- spring的思路是给已经初始化的bean标记状态,假设A依赖B,B依赖A,先创建A
- 先从缓存容器(总共三层,一级拿不到就拿二级,二级拿不到就从三级缓存中拿正在创建的)中获取A,未获取到就执行创建逻辑
- 对象A在创建完成还未将属性渲染完之前标记为正在创建中,放入三级缓存容器。渲染属性populateBean()会获取依赖的对象B。
- 此时B会走一次getBean逻辑,B同样会先放入三级缓存,然后渲染属性,再次走getBean逻辑注入A,此时能从三级缓存中拿到A,并将A放入二级容器。B渲染完成放入一级容器
- 回到A渲染B的方法populateBean(),拿到B之后能顺利执行完自己的创建过程。放入一级缓存
为了证实结果,我把源码给改了一下,看结果
package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/18.
*/
@Component("a")
public class CircularA { @Resource
private CircularB circularB; @Override
public String toString() {
return "CircularA{" +
"circularB=" + circularB +
'}';
}
} package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/18.
*/
@Component("b")
public class CircularB { @Resource
private CircularA circularA; @Override
public String toString() {
return "CircularB{" +
"circularA=" + circularA +
'}';
}
} 测试代码
@Test
public void testCircular() {
String basePackages = "com.vip.qc.circular";
new AnnotationConfigApplicationContext(basePackages);
} 测试结果(我改过源码了)
----
将a放入三级缓存
将b放入三级缓存
将a放入二级缓存
将b放入一级缓存
从二级缓存中拿到了a
将a放入一级缓存
- 再看源码
- 关键类处理getSingleton逻辑 - 缓存容器
public class DefaultSingletonBeanRegistry /** 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);
- 主流程 AbstractApplicationContext#refresh() -> DefaultListableBeanFactory#preInstantiateSingletons() -> AbstractBeanFactory#getBean() & #doGetBean()
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
/**
* 处理FactoryBean接口名称转换 {@link BeanFactory#FACTORY_BEAN_PREFIX }
*/
final String beanName = transformedBeanName(name);
...
// ①从缓存中拿对象(如果对象正在创建中且被依赖注入,会放入二级缓存)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
...
}else {
...
if (mbd.isSingleton()) {
// ② 将创建的对象放入一级缓存
sharedInstance = getSingleton(beanName, () -> {
try {
// ③ 具体创建的过程,每个bean创建完成之后都会放入三级缓存,然后渲染属性
return createBean(beanName, mbd, args);
}catch (BeansException ex) {
...
...
return (T) bean;
}
- ①getSingleton(beanName) - 二级缓存操作
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 实例化已经完成了的放在singletonObjects
Object singletonObject = this.singletonObjects.get(beanName);
// 解决循环依赖
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
System.out.println("将"+beanName+"放入二级缓存");;
this.singletonFactories.remove(beanName);
}
}else if(singletonObject != null){
System.out.println("从二级缓存中拿到了"+beanName);
}
}
}
return singletonObject;
}
- ② getSingleton(beanName,lamdba) - 一级缓存操作
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
...
// 正在创建的bean加入singletonsCurrentlyInCreation - 保证只有一个对象创建,阻断循环依赖
beforeSingletonCreation(beanName);
...
try {
singletonObject = singletonFactory.getObject();
...
finally {
...
// 从singletonsCurrentlyInCreation中移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 对象创建完毕 - 放入一级缓存(从其他缓存移除)
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
} // ----- 内部调用一级缓存操作
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
System.out.println("将"+beanName+"放入一级缓存");;
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
- ③createBean(beanName, mbd, args) - 三级缓存操作
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {
...
if (instanceWrapper == null) {
// 5* 实例化对象本身
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
...
// 将创建好还未渲染属性的bean 放入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
} Object exposedObject = bean;
try {
// 渲染bean自身和属性
populateBean(beanName, mbd, instanceWrapper);
// 实例化之后的后置处理 - init
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
...
return exposedObject;
} // ------------- 内部调用三级缓存操作
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
System.out.println("将"+beanName+"放入三级缓存");;
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
- 关键类处理getSingleton逻辑 - 缓存容器
菜瓜:demo比较简单,流程大致明白,源码我还需要斟酌一下,整体有了概念。这个流程好像是掺杂在bean的创建过程中,结合bean的生命周期整体理解可能会更深入一点
水稻:是的。每个知识点都不是单一的,拿着bean的生命周期再理解一遍可能会更有收获。
讨论
- 为什么是三级缓存,两级不行吗?
- 猜测:理论上两级也可以实现。多一个二级缓存可能是为了加快获取的速度。加入A依赖B,A依赖C,B依赖A,C依赖A,那么C在获取A的时候只需要从二级缓存中就能拿到A了
总结
- Spring的处理方式和java处理的思想一致,构造器依赖本身是破坏语义和规范的
- 属性赋值--> 依赖注入 。 先创建对象,再赋值属性,赋值的时候发现需要创建便生成依赖对象,被依赖对象需要前一个对象就从缓存容器中拿取即可
【spring】循环依赖 Java Vs Spring的更多相关文章
- spring循环依赖问题分析
新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...
- Spring循环依赖
Spring-bean的循环依赖以及解决方式 Spring里面Bean的生命周期和循环依赖问题 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环. ...
- Spring 循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...
- Spring循环依赖的三种方式以及解决办法
一. 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的 ...
- Spring循环依赖的解决
## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...
- 这个 Spring 循环依赖的坑,90% 以上的人都不知道
1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...
- 烂大街的 Spring 循环依赖问题,你觉得自己会了吗
文章已收录在 GitHub JavaKeeper ,N 线互联网开发.面试必备技能兵器谱,笔记自取. 微信搜「 JavaKeeper 」程序员成长充电站,互联网技术武道场.无套路领取 500+ 本电子 ...
- 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...
- 图解 Spring 循环依赖,写得太好了!
Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...
随机推荐
- 循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用
在我们开发Vue应用的时候,很多时候需要记录一些变量的内容,这些可以用来做界面状态的承载,也可以作为页面间交换数据的处理,处理这些内容可以归为Vuex的状态控制.例如我们往往前端需要访问后端数据,一般 ...
- iOS开发Runtime 方法替换
通过#import <objc/runtime.h>我们可以找到: /** * Returns a specified instance method for a given class. ...
- [工具推荐]005.Axure RP Pro 7.0模拟C#TAB控件
有一次,主管安排我写一个项目的原型,但是项目中涉及到了Tab控件,在Axure中的控件中找了一番,没有找着Tab控件.那么我们只能换种法子来实现它了,我们用到了Dynamic Panel来模拟. 1. ...
- 03 . MysSQL权限和备份
MySQL权限介绍 mysql中存在4个控制权限的表,分别为 user db tables_priv columns_priv 用户和权限内容放在mysql库中,该库中有一张名为user的表,记录了用 ...
- 跟着阿里学JavaDay04——Java基础语法(二)
一.Java运算符 运算符的简介 所有的程序开发,都是对数字的进行处理.而对数字的处理,一定会存在所谓的操作模式,而这些操作模式就被称为运算符. 算术运算符 对于算术运算符,我们主要掌握++,--的运 ...
- Spring boot Sample 009之spring-boot-web-thymeleaf
一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 spring boot 整合thymeleaf模板开发web项目 三.步骤 3.1.点击File -> New Pro ...
- 快速复习C语言 - 1变量与运算符
变量与运算符 本篇以读者知道 int.char.float.double 等数据类型为前提条件. float 类型注意事项 float 类型数没有办法跟一个数真正比较是否相等,可以定义借助绝对值在一定 ...
- ActiveMQ 笔记(五)ActiveMQ的传输协议
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 面试思考题: 默认的61616端口如何更改 你生产上的连接协议如何配置的?使用tcp吗? 一.Activ ...
- Java实现 LeetCode 668 乘法表中第k小的数(二分)
668. 乘法表中第k小的数 几乎每一个人都用 乘法表.但是你能在乘法表中快速找到第k小的数字吗? 给定高度m .宽度n 的一张 m * n的乘法表,以及正整数k,你需要返回表中第k 小的数字. 例 ...
- Java实现高效便捷还容易懂的排序算法
PS:我现在越来越认为排序大法是,很深的算法了,就是简单的几个步骤,网上的大佬们能给你玩出花来(ง •_•)ง public class zimuzhenlie2 { public static vo ...