1、循环依赖

(1)概念

对象依赖分为强依赖和弱依赖:

强依赖指的是一个对象包含了另外一个对象的引用,例如:学生类中包含了课程类,在学生类中存在课程类的引用

创建课程类:

  1. @Data
  2. public class Course {
  3. private String cname;
  4. private Integer credit;
  5. }

创建学生类:

  1. @Data
  2. public class Student {
  3. private String sname;
  4. private int age;
  5. private Course course;
  6. }

测试类:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Student student=new Student();
  4. Course course=new Course();
  5. student.setCourse(course);
  6. }
  7. }

弱依赖指的是一个对象里面调用了另外一个对象

循环依赖:A引用B,而B又引用A

创建课程类,课程类中有学生类的引用:

  1. @Data
  2. public class Course {
  3. private String cname;
  4. private Integer credit;
  5. private Student student;
  6. }

创建学生类,学生类中存在课程类中的引用:

  1. @Data
  2. public class Student {
  3. private String sname;
  4. private int age;
  5. private Course course;
  6. }

创建测试类,测试类中学生类调用课程类的对象,课程类的对象又调用学生类的对象:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Student student=new Student();
  4. Course course=new Course();
  5. student.setCourse(course);
  6. course.setStudent(student);
  7. }
  8. }

测试类可以正常地获取对象,也就是说这种方式是支持循环依赖的。

(2)spring的循环依赖

  1. <bean id="student" class="com.zhb.bean.Student" scope="singleton">
  2. <property name="course" ref="course"></property>
  3. </bean>
  4. <bean id="course" class="com.zhb.bean.Course" scope="singleton">
  5. <property name="student" ref="student"></property>
  6. </bean>

单例模式下支持,多例模式下不支持

(3)spring是否支持循环依赖

支持

(4)spring的所有对象是不是都支持循环依赖

单例模式下支持:单例模式下是首先创建出两个对象,然后进行依赖的注入。A注入B,B注入A。

多例模式下不支持:A创建的时候会去判断是否依赖于其他对象,如果依赖的话就不会去先创建A。创建B的时候也会去判断是否依赖其他对象,如果A依赖于B,而B又依赖于A的话就会造成一个死循环,两个对象都不会创建。

2、spring循环依赖的过程

(1)流程

(2)源码

ClassPathXmlApplicationContext方法:

  1. public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
  2. super(parent);
  3. this.setConfigLocations(configLocations);
  4. if (refresh) {
  5. this.refresh();//refresh方法是真正的创建对象的方法
  6. }
  7.  
  8. }

refresh方法:

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized(this.startupShutdownMonitor) {
  3. this.prepareRefresh();
  4. ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
  5. this.prepareBeanFactory(beanFactory);
  6.  
  7. try {
  8. this.postProcessBeanFactory(beanFactory);
  9. this.invokeBeanFactoryPostProcessors(beanFactory);//Bean工厂的后置处理器
  10. this.registerBeanPostProcessors(beanFactory);
  11. this.initMessageSource();
  12. this.initApplicationEventMulticaster();
  13. this.onRefresh();
  14. this.registerListeners();
  15. this.finishBeanFactoryInitialization(beanFactory);//此方法是真正的创建对象的方法,能够实例化所有的单例bean
  16. this.finishRefresh();
  17. } catch (BeansException var9) {
  18. if (this.logger.isWarnEnabled()) {
  19. this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
  20. }
  21.  
  22. this.destroyBeans();
  23. this.cancelRefresh(var9);
  24. throw var9;
  25. } finally {
  26. this.resetCommonCaches();
  27. }
  28.  
  29. }
  30. }

finishBeanFactoryInitialization

  1. protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  2. if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {
  3. beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));
  4. }
  5.  
  6. if (!beanFactory.hasEmbeddedValueResolver()) {
  7. beanFactory.addEmbeddedValueResolver((strVal) -> {
  8. return this.getEnvironment().resolvePlaceholders(strVal);
  9. });
  10. }
  11.  
  12. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
  13. String[] var3 = weaverAwareNames;
  14. int var4 = weaverAwareNames.length;
  15.  
  16. for(int var5 = 0; var5 < var4; ++var5) {
  17. String weaverAwareName = var3[var5];
  18. this.getBean(weaverAwareName);
  19. }
  20.  
  21. beanFactory.setTempClassLoader((ClassLoader)null);
  22. beanFactory.freezeConfiguration();
  23. beanFactory.preInstantiateSingletons();
  24. }

接口:

  1. void preInstantiateSingletons() throws BeansException;
  1. public void preInstantiateSingletons() throws BeansException {
  2. if (this.logger.isTraceEnabled()) {
  3. this.logger.trace("Pre-instantiating singletons in " + this);
  4. }
  5.  
  6. List<String> beanNames = new ArrayList(this.beanDefinitionNames);
  7. Iterator var2 = beanNames.iterator();//取出名字
  8.  
  9. while(true) {
  10. String beanName;
  11. Object bean;
  12. do {
  13. while(true) {
  14. RootBeanDefinition bd;
  15. do {
  16. do {
  17. do {
  18. if (!var2.hasNext()) {
  19. var2 = beanNames.iterator();
  20.  
  21. while(var2.hasNext()) {
  22. beanName = (String)var2.next();
  23. Object singletonInstance = this.getSingleton(beanName);
  24. if (singletonInstance instanceof SmartInitializingSingleton) {
  25. SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton)singletonInstance;
  26. if (System.getSecurityManager() != null) {
  27. AccessController.doPrivileged(() -> {
  28. smartSingleton.afterSingletonsInstantiated();
  29. return null;
  30. }, this.getAccessControlContext());
  31. } else {
  32. smartSingleton.afterSingletonsInstantiated();
  33. }
  34. }
  35. }
  36.  
  37. return;
  38. }
  39.  
  40. beanName = (String)var2.next();
  41. bd = this.getMergedLocalBeanDefinition(beanName);
  42. } while(bd.isAbstract());//单例抽象懒加载
  43. } while(!bd.isSingleton());
  44. } while(bd.isLazyInit());
  45.  
  46. if (this.isFactoryBean(beanName)) {
  47. bean = this.getBean("&" + beanName);
  48. break;
  49. }
  50.  
  51. this.getBean(beanName);
  52. }
  53. } while(!(bean instanceof FactoryBean));
  54.  
  55. FactoryBean<?> factory = (FactoryBean)bean;
  56. boolean isEagerInit;
  57. if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
  58. SmartFactoryBean var10000 = (SmartFactoryBean)factory;
  59. ((SmartFactoryBean)factory).getClass();
  60. isEagerInit = (Boolean)AccessController.doPrivileged(var10000::isEagerInit, this.getAccessControlContext());
  61. } else {
  62. isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)factory).isEagerInit();
  63. }
  64.  
  65. if (isEagerInit) {
  66. this.getBean(beanName);
  67. }
  68. }
  69. }

getSingleton方法:

  1. @Nullable
  2. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  3. Object singletonObject = this.singletonObjects.get(beanName);//
  4. if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
  5. synchronized(this.singletonObjects) {
  6. singletonObject = this.earlySingletonObjects.get(beanName);
  7. if (singletonObject == null && allowEarlyReference) {
  8. ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
  9. if (singletonFactory != null) {
  10. singletonObject = singletonFactory.getObject();
  11. this.earlySingletonObjects.put(beanName, singletonObject);
  12. this.singletonFactories.remove(beanName);
  13. }
  14. }
  15. }
  16. }
  17.  
  18. return singletonObject;
  19. }

3、怎么避免循环依赖?

三级缓存

  • 一级缓存:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用。
  • 二级缓存:earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean
  • 三级缓存:singletonFactories,三级缓存,存放实例化完成的Bean工厂。

假设A依赖B,B依赖A(注意:这里是set属性依赖)分以下步骤执行:

  A依次执行doGetBean、查询缓存、createBean创建实例,实例化完成放入三级缓存singletonFactories中,接着执行populateBean方法装配属性,但是发现有一个属性是B的对象。因此再次调用doGetBean方法创建B的实例,依次执行doGetBean、查询缓存、createBean创建实例,实例化完成之后放入三级缓存singletonFactories中,执行populateBean装配属性,但是此时发现有一个属性是A对象。

  因此再次调用doGetBean创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例(早期引用,未完成属性装配),此时直接返回A,不用执行后续的流程创建A了,那么B就完成了属性装配,此时是一个完整的对象放入到一级缓存singletonObjects中。

  B创建完成了,则A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中。

spring怎么避免循环依赖的更多相关文章

  1. Spring源代码解析 ---- 循环依赖

    一.循环引用: 1. 定义: 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比方CircularityA引用CircularityB,CircularityB引用Circularit ...

  2. Spring源码-循环依赖源码解读

    Spring源码-循环依赖源码解读 笔者最近无论是看书还是从网上找资料,都没发现对Spring源码是怎么解决循环依赖这一问题的详解,大家都是解释了Spring解决循环依赖的想法(有的解释也不准确,在& ...

  3. Spring中的循环依赖解决详解

    前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文 ...

  4. Spring 如何解决循环依赖问题?

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...

  5. Spring如何解决循环依赖问题

    目录 1. 什么是循环依赖? 2. 怎么检测是否存在循环依赖 3. Spring怎么解决循环依赖 本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可 ...

  6. Spring 如何解决循环依赖的问题

    Spring 如何解决循环依赖的问题 https://blog.csdn.net/qq_36381855/article/details/79752689 Spring IOC 容器源码分析 - 循环 ...

  7. Spring如何解决循环依赖?

    介绍 先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成 Spring的循环依赖有两种场景 构造器的循环依赖 ...

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

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

  9. 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖

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

随机推荐

  1. IdentityServer4网页(单点)登陆入门

    前言 本篇说说ids中的网页登陆以及单点登陆的大致原理,主要是以基本跑通为目的,下一篇开始会详细说明集成ids网页登陆原理. 最好先熟悉以下知识: asp.net core asp.net core的 ...

  2. The Successor Representation: Its Computational Logic and Neural Substrates

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Received May 14, 2018; revised June 28, 2018; accepted July 5, 2018.T ...

  3. promise的常用情况

    因为js是单线程的,所以一旦代码中有报错,就不会执行下面的了,如下333就未打印 console.log(111)throw Error(222)console.log(333) 好像与promise ...

  4. 七夕节来啦!AI一键生成情诗,去发给你的女朋友吧!

    [摘要] Hello大家好,今天就是七夕节了,为了增进和女朋友之间的情感,我写了一个自动生成情诗的AI: 大家可以在ModelArts尝试复现模型,然后快去发给你们的女朋友吧- 大家好,我是b站up主 ...

  5. 洛谷 P3951 NOIP 2017 小凯的疑惑

    洛谷 P3951 NOIP 2017 小凯的疑惑 题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付 ...

  6. istio的安全(概念)

    Istio 安全(概念) 目录 Istio 安全(概念) 高层架构 Istio身份 身份和证书管理 认证 Mutial TLS认证 宽容(Permissive)模式 安全命名 认证架构 认证策略 策略 ...

  7. 大数据计算的基石——MapReduce

    MapReduce Google File System提供了大数据存储的方案,这也为后来HDFS提供了理论依据,但是在大数据存储之上的大数据计算则不得不提到MapReduce. 虽然现在通过框架的不 ...

  8. MSP430-LED中断闪烁代码详解

    使用MSP430F149的开发板,首先对LED闪烁灯的例程进行讲解,然后下边是自己写的,将部分代码写入了新建的led.c程序中 #include  <msp430x14x.h>       ...

  9. Vuex 注入 Vue 生命周期的过程

    首先我们结合 Vue 和 Vuex 的部分源码,来说明 Vuex 注入 Vue 生命周期的过程. 说到源码,其实没有想象的那么难.也和我们平时写业务代码差不多,都是方法的调用.但是源码的调用树会复杂很 ...

  10. MySql密码的问题

    由于长时间没使用过MySql了,也由于之前没有做笔记的习惯,晚上因为MySQL的密码问题导致数据库长时间没连上.纠结了这么久还是决定记录下来,毕竟安装的东西多了,这年头到处都是密码,加上时间一长,很容 ...