首先,有两种Bean注入的方式:构造器注入和属性注入。

  1. 对于构造器注入的循环依赖,Spring处理不了,会直接抛出BeanCurrentlylnCreationException异常。
  2. 对于属性注入的循环依赖
    1. 单例模式下,是通过三级缓存处理来循环依赖的。
    2. 非单例对象的循环依赖,则无法处理。

单例模式下的属性依赖

先来看下这三级缓存

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); /** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  • 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象
  • 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象
  • 第三层缓存(singletonFactories): 单例工厂的缓存

如下是获取单例中:

getSingleton 在 doGetBean方法中调用

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Spring首先从singletonObjects(一级缓存)中尝试获取
Object singletonObject = this.singletonObjects.get(beanName);
// 若是获取不到而且对象在建立中,则尝试从earlySingletonObjects(二级缓存)中获取
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) {
//若是仍是获取不到而且容许从singletonFactories经过getObject获取,则经过singletonFactory.getObject()(三级缓存)获取
singletonObject = singletonFactory.getObject();
//若是获取到了则将singletonObject放入到earlySingletonObjects,也就是将三级缓存提高到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

补充一些方法和参数

  • isSingletonCurrentlyInCreation():判断当前单例bean是否正在建立中,也就是没有初始化完成(好比A的构造器依赖了B对象因此得先去建立B对象, 或则在A的populateBean过程当中依赖了B对象,得先去建立B对象,这时的A就是处于建立中的状态。)
  • allowEarlyReference :是否容许从singletonFactories中经过getObject拿到对象

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。若是获取不到,而且对象正在建立中,就再从二级缓存earlySingletonObjects中获取。若是仍是获取不到且容许singletonFactories经过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,若是获取到了则从三级缓存移动到了二级缓存。

从上面三级缓存的分析,咱们能够知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义以下:

public interface ObjectFactory<T> {
T getObject() throws BeansException;
}

在bean建立过程当中,有两处比较重要的匿名内部类实现了该接口。一处是Spring利用其建立bean的时候,另外一处就是:

addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}});

此处就是解决循环依赖的关键,这段代码发生在createBeanInstance以后,也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了,虽然还不完美(尚未进行初始化的第二步和第三步),可是已经能被人认出来了(根据对象引用能定位到堆中的对象),因此Spring此时将这个对象提早曝光出来让你们认识,让你们使用。

好比“A对象setter依赖B对象,B对象setter依赖A对象”,A首先完成了初始化的第一步,而且将本身提早曝光到singletonFactories中,此时进行初始化的第二步,发现本身依赖对象B,此时就尝试去get(B),发现B尚未被create,因此走create流程,B在初始化第一步的时候发现本身依赖了对象A,因而尝试get(A),尝试一级缓存singletonObjects(确定没有,由于A还没初始化彻底),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,因为A经过ObjectFactory将本身提早曝光了,因此B可以经过ObjectFactory.getObject拿到A对象(半成品),B拿到A对象后顺利完成了初始化阶段一、二、三,彻底初始化以后将本身放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成本身的初始化阶段二、三,最终A也完成了初始化,进去了一级缓存singletonObjects中,并且更加幸运的是,因为B拿到了A的对象引用,因此B如今hold住的A对象完成了初始化。

为什么不能解决非单例属性之外的循环依赖?

  1. 为何不能解决构造器的循环依赖?

    1. 构造器注入形成的循环依赖: 也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。
    2. Spring解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。
  2. 为什么不能解决prototype作用域循环依赖?
    1. 这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的
  3. 为什么不能解决多例的循环依赖?
    1. 多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。

其它循环依赖如何解决

这类循环依赖问题解决方法很多,主要有:

  • 使用@Lazy注解,延迟加载

    • 构造器循环依赖这类循环依赖问题可以通过使用@Lazy注解解决
  • 使用@DependsOn注解,指定加载先后关系
    • 使用@DependsOn产生的循环依赖:这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
  • 修改文件名称,改变循环依赖类的加载顺序
  • 多例循环依赖这类循环依赖问题可以通过把bean改成单例的解决。

Spring中的循环依赖是怎么个事?的更多相关文章

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

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

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

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

  3. 从一部电影史上的趣事了解 Spring 中的循环依赖问题

    title: 从一部电影史上的趣事了解 Spring 中的循环依赖问题 date: 2021-03-10 updated: 2021-03-10 categories: Spring tags: Sp ...

  4. 【Spring】Spring中的循环依赖及解决

    什么是循环依赖? 就是A对象依赖了B对象,B对象依赖了A对象. 比如: // A依赖了B class A{ public B b; } // B依赖了A class B{ public A a; } ...

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

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

  6. 一起来踩踩 Spring 中这个循环依赖的坑

    1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什么要依赖注入 6. Spring的依赖注入模型 7. 非典型问题 参考资料 1. 前言 这两天工作遇到了一个挺有意思的Sp ...

  7. Spring中的循环依赖

    循环依赖 在使用Spring时,如果主要采用基于构造器的依赖注入方式,则可能会遇到循环依赖的情况,简而言之就是Bean A的构造器依赖于Bean B,Bean B的构造器又依赖于Bean A.在这种情 ...

  8. Spring中解决循环依赖报错的问题

    什么是循环依赖 当一个ClassA依赖于ClassB,然后ClassB又反过来依赖ClassA,这就形成了一个循环依赖: ClassA -> ClassB -> ClassA 原创声明 本 ...

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

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

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

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

随机推荐

  1. List接口和Deque接口的ArrayList和Vetor,LinkedList及ArrayDeque等实现类

    ArrayList和Vetor是List两个典型的实现类. 他们都是基于数组的实现类.内部都封装了一个动态的,允许再分配的Object[]数组. 他两方法基本都相同.除了,ArrayList是线程不安 ...

  2. CF757G Can Bash Save the Day? (复健 Day 1)

    先差分为 \(Q(r)-Q(l-1)\),\(Q(i)=\sum_{j=1}^{i} \operatorname{dis}(p_j, x)\). 树上在线路径优先考虑点分树,先想询问怎么做,我们记 \ ...

  3. 安装Microsoft Visio 2016,激活的报错

    Microsoft Visio 2016安装激活教程 我在安装序列号的时候出现了报错. The Software Licensing Service reported that the product ...

  4. homeomorphic 同胚 释义

    简介 在拓扑学中,两个流形,如果可以通过弯曲.延展.剪切(只要最终完全沿着当初剪开的缝隙再重新粘贴起来)等操作把其中一个变为另一个,则认为两者是同胚的.如:圆和正方形是同胚的,而球面和环面就不是同胚的 ...

  5. 20250709 - GMX V1 攻击事件: 重入漏洞导致的总体仓位价值操纵

    背景 2025 年 7 月 9 日,GMX V1 遭受黑客攻击,损失约 4200 万美元资产.攻击者利用 executeDecreaseOrder 函数发送 ETH 的行为进行重入,绕过 enable ...

  6. Linguistics-English-区分 that Vs. which + 定语(refine限定主句)从句 Vs. 同位语(expand扩展补充主句)从句

    英语的"语法"适配"含义" 英语是一门语言,有丰富的含义: "语法结构" 是与 "含义"适配使用的,而不只是背语法规则: ...

  7. .NETCore文件上传将文件保存到docker容器以外的文件夹

    最近在开发一个文件服务,用于公司内容各应用的文件保存和查询获取. 开发环境:windows10+.NET Core7.0+Mysql   发布环境 :Liunx+Docker 实现功能:文件服务提供接 ...

  8. 亚马逊与马普学会共建AI科学中心

    亚马逊与马克斯·普朗克学会(简称马普学会或MPG)今日宣布成立联合科学中心.这是亚马逊在美国境外设立的首个科学中心,将致力于推动德国全境的人工智能研发. 该中心旨在突破人工智能.计算机视觉和机器学习的 ...

  9. mysql备份工具和策略

    mysqldump备份工具: 备份整个数据库: mysqldump  --all-databases > dump.sql 包含存储过程和事件的备份如下: mysqldump --all-dat ...

  10. Influxdb订阅与kapacitor使用梳理

    转载请注明出处: 一.订阅功能的核心作用 InfluxDB 的订阅是一种 数据自动推送机制,当指定数据库的写入操作发生时,InfluxDB 会 实时复制数据 并推送到预先配置的端点(如 Kapacit ...