文章目录

  • 1、什么是循环依赖?
  • 2、循环依赖纯java实验
  • 3、A / B两对象在三级缓存中的迁移说明
  • 4、spring三级缓存解决循环依赖的总结

1、什么是循环依赖?

多个bean之间相互依赖,形成了一个闭环。比如:A依赖于B、B依赖于C、C依赖于A。

通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。

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

循环依赖官网说明:

结论:

我们AB循环依赖问题只要A的注入方式是setter且singleton ,就不会有循环依赖问题。

2、循环依赖纯java实验

循环依赖现象在spring容器中注入依赖的对象,有2种情况

  • 构造器方式注入依赖(不可行)
  • 以set方式注入依赖(可行)
//构造器方式注入依赖(不可行)
@Component
public class ServiceB{private ServiceA serviceA;public ServiceB(ServiceA serviceA){this.serviceA = serviceA;}
}@Component
public class ServiceA{private ServiceB serviceB;public ServiceA(ServiceB serviceB){this.serviceB = serviceB;}
}public class ClientConstructor{public static void main(String[] args){new ServiceA(new ServiceB(new ServiceA()));//这会抛出编译异常}
}

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

@Component
public class ServiceBB{private ServiceAA serviceAA;public void setServiceAA(ServiceAA serviceAA){this.serviceAA = serviceAA;System.out.println("B里面设置了A");}
}@Component
public class ServiceAA{private ServiceBB serviceBB;public void setServiceBB(ServiceBB serviceBB){this.serviceBB = serviceBB;System.out.println("A里面设置了B");}
}public class ClientSet{public static void main(String[] args){//创建serviceAAServiceAA a = new ServiceAA();//创建serviceBBServiceBB b = new ServiceBB();//将serviceA入到serviceB中b.setServiceAA(a);//将serviceB法入到serviceA中a.setServiceBB(b);}
}

输出结果:

B里面设置了A
A里面设置了B

spring循环依赖bug演示:
beans:A,B

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.");}
}
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.");}
}

运行类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class ClientSpringContainer {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");A a = context.getBean("a", A.class);B b = context.getBean("b", B.class);}
}

默认的单例(Singleton)的场景是支持循环依赖的,不报错
beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.0.xsd"><bean id="a" class="com.lun.interview.circular.A"><property name="b" ref="b"></property></bean><bean id="b" class="com.lun.interview.circular.B"><property name="a" ref="a"></property></bean></beans>

输出结果

00:00:25.649 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6d86b085
00:00:25.828 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [beans.xml]
00:00:25.859 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
00:00:25.875 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
B call setA.
A call setB.

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

beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.0.xsd"><bean id="a" class="com.lun.interview.circular.A" scope="prototype"><property name="b" ref="b"></property></bean><bean id="b" class="com.lun.interview.circular.B" scope="prototype"><property name="a" ref="a"></property></bean></beans>

输出结果

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

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

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

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

  • 第三级缓存:Map<String, ObjectFactory<?>> singletonFactories,存放可以生成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);...
}

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

1、A创建过程中需要B,于是A将自己放到三级缓里面,去实例化B。2、B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,
再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,
此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

4、spring三级缓存解决循环依赖的总结

Spring创建 bean主要分为两个步骤:

1、创建原始bean对象2、接着去填充对象属性和初始化

每次创建 bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个

当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完beanB填充属性时又发现它依赖了beanA又是同样的流程,
不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA.所以不需要继续创建,用它注入 beanB,完成 beanB的创建

既然 beanB创建好了,所以 beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

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()方法从缓存中查找beanA2、在getSingleton()方法中,从一级缓存中查找,没有,返回null3、doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,
调用getSingleton()的重载方法(参数为ObjectFactory的)4、在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。
然后回调匿名内部类的creatBean方法5、进入AbstractAutowireCapableBeanFactory-->ndoCreateBean,
先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用
(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。
判断为true则将beanA添加到【三级缓存】中6、对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB7、调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性8、此时 beanB依赖于beanA,调用getSingleton()获取beanA,
依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,
通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA9、这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,
并将beanA从三级缓存移动到二级缓存中10、随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,
回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

21、spring循环依赖 问题相关推荐

  1. 这个Spring循环依赖的坑,90%以上的人都不知道

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Mythsman 原文:https://blog.myths ...

  2. 烂大街的Spring循环依赖该如何回答?

    什么是循环依赖? 从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身.体现到代码层次就是这个样子 @Component public class A {// A中注入了B ...

  3. 【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)

    本次我们通过源码介绍ApplicationContext容器初始化流程,主要介绍容器内bean的实例化和初始化过程.ApplicationContext是Spring推出的先进Ioc容器,它继承了旧版 ...

  4. 终于有人把 Spring 循环依赖讲清楚了!

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  5. spring处理循环依赖时序图_spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖...

    本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...

  6. Spring循环依赖源码剖析

    Spring循环依赖源码剖析 一.场景介绍 二.整理执行流程总结 三.源码分析 编写测试类 /*** 测试循环依赖*/@Testpublic void testCyclicDependence(){A ...

  7. Spring 循环依赖(circular dependency)

    一.什么是循环依赖 循环依赖即循环引用,形成闭环.比如,A 依赖 B,B 又依赖 A,形成了循环依赖:或者 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖:更或者是自己依赖自己.如图: 这 ...

  8. spring循环依赖让你更好的理解spring!!

    什么是循环依赖 一言以蔽之:两者相互依赖. 在开发中,可能经常出现这种情况,只是我们平时并没有注意到原来我们写的两个类.甚至多个类相互依赖了,为什么注意不到呢?当然是因为没有报错,而且一点问题都木有, ...

  9. 帮助你更好的理解Spring循环依赖

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

最新文章

  1. mysql 判断表或字段存不存在
  2. python construct 字符串_通过字符串变量在Python中设置和获取@property方法
  3. 初等数论--二次剩余与二次同余方程--既约剩余系中二次剩余的个数
  4. Scala函数柯里化
  5. php抢购排队是怎样做的,基于swoole的抢购排队通用中间件,适合抢购秒杀场景,跟具体业务解耦...
  6. Puzzle 18 - StringCheese - byte storage
  7. 如何修改myeclipse中web项目的工作路径或默认路径
  8. 导航栏下拉至一定高度后固定在顶部的特效
  9. Socket一次Recv接受的字节有限制么?
  10. 高仿wx钱包页H5网站源码
  11. 调用门、堆栈切换与调用过程返回
  12. FPDF中文应用攻略
  13. JPA学习笔记---JPA实体Bean的建立---链接上一个博文:对实体Bean中属性进行操作:保存日期类型,设置字段的长度,名字,是否为空,可以声明枚举字段;可以存放二进制数据,可以存放
  14. sublime3103 破解及Package Control离线安装
  15. 大数据/数据挖掘/推荐系统/机器学习相关资源
  16. 计算机组成知识教案,计算机系统的基本组成 教案_
  17. Volatile关键字~转载自博客园的“海子”
  18. 使用Windows平台的VS2022来调试AKStream
  19. 互联网造车最靠谱的方式是合作
  20. HTML:桂林山水风景Web界面设计

热门文章

  1. 用于汇报的几个视频编辑工具
  2. 如何DIY一个天线分析仪
  3. python RSA 公钥解密方法
  4. [转]LSM-Tree (BigTable 的理论模型)
  5. 干细胞领域国际大佬盘点
  6. android 方法统计,神兵利器—Android方法耗时统计插件Mirror(上)
  7. 数字出版是个伪概念,没有钱途
  8. 恒流、限流功能驱动 WLED 方案
  9. 网络安全课程day1
  10. speedoffice(PPT)图片怎么旋转方向