不是说Spring三级缓存可以解决循环依赖吗?怎么我写的就GG了

Spring 解决循环依赖过程 有构造器就无法解决循环依赖? 一定要三级缓存才能解决循环依赖? 到底为什么要三级缓存? 有构造器就无法解决循环依赖? 一定要三级缓存才能解决循环依赖? 到底为什么要三级缓存? @Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; } @Service public class A { @Autowired . 阅读详情

大家都知道Spring 通过三级缓存来解决循环依赖问题,但是我今天写出来个循环依赖,直接报错,难受啊,三级缓存并没有被关闭,本篇文章我们就来深入分析一下三级缓存,以及为啥我的项目启动不了?

1. 什么是循环依赖?

上代码!

@Service
class ServiceA {
    @Autowired
    ServiceB serviceB;
    
    public void test() {
    }
}
​
@Service
class ServiceB {
    @Autowired
    ServiceA serviceA;
    
    public void test() {
    }
}
复制代码

简单的说就是你中有我,我中有你,

当然也不排除你中有我,我中有她,她中有你这种复杂,甚至是更复杂的关系。

总之循环依赖就是会形成一个闭合的环形关系。

2. 循环依赖带来什么问题?

循环依赖会导致创建对象,进入死循环状态,无法创建成功。

在实例化对象A的时候,需要注入对象B,这时我们需要检查缓存中是否有对象B,如果缓存中没有对象B,这时我们需要实例化对象B,开始实例化对象B,对象B中又需要依赖A,但是这时缓存又没有构建好的A,又需要实例化A,实例化A又依赖B,B又依赖A,形成死循环,永远都不能成功创建A,B。

先实例化对象B于先实例化对象A是一样的。

image.png

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

基于Java引用传递的特性,我们是可以通过缓存来解决循环依赖,我们可以将构建一半的对象放入缓存中,先进行下边的步骤,当下边的步骤创建完成后,缓存中的对象自然是完整的对象,因为缓存中我们只是存的引用地址,当对象创建好之后,半成品的对象就会变成成品对象。

3.1 一级缓存解决循环依赖

我们只通过一级缓存也是可以解决循环依赖问题的,但是会有很多问题,不够严谨,比如缓存中的对象有半成品,如果我们的对象没有打对应的标签,就可能会获取到半成品对象,引发一些诡异的问题。

  • 无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象)
  • 无法清晰明确的区分出,哪个对象已经构建完成

image.png

3.2 二级缓存解决循环依赖问题

通过二级缓存其实我们已经可以很好的解决循环依赖问题,但是我们还是无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象),通过缓存解决循环依赖问题,要求我们注入的对象必须是代理对象,才能实现增强的功能。

那么我们能不能在发生循环依赖的时候才去提前初始化代理对象呢?

那么我们什么时候才能知道发生了循环依赖呢?

当依赖注入时,我们从二级缓存中获取到对象,这时候就能判定发生了循环依赖,所以说我们在这时候构建代理对象。

如果没有发生循环依赖,我们是不需要从二级缓存中获取值的。

如果说我们在二级缓存中放入ObjectFactory,当从二级缓存中获取值的时候再通过工厂创建对象,这样不就可以解决循环依赖了吗?

看似这样可以解决循环依赖问题,并且可以在发生循环依赖的时候创建代理对象,但是新的问题又出现了。

新问题:如果是A依赖于B和C,而B依赖于A,同时C也依赖于A。

那么如果先创建的是A,那么创建A的时候会在二级缓存中生成A,那么创建B的时候也会去二级缓存获取A,创建C的时候也会去二级缓存中获取A获取到的

image.png

3.3 三级缓存解决循环依赖问题

Spring默认提供三级缓存解决循环依赖

image.png

4. Spring三级缓存源码分析

以下便是Spring中的源码

定义三级缓存

/** 三级缓存 */ 
/** 单例池 */ 
/** 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 ConcurrentHashMap<>(16);
复制代码

获取单例对象,单例模式(懒加载双重检测锁)

    /**
     * Return the (raw) singleton object registered under the given name.
     * <p>Checks already instantiated singletons and also allows for an early
     * reference to a currently created singleton (resolving a circular reference).
     * @param beanName the name of the bean to look for
     * @param allowEarlyReference whether early references should be created or not
     * @return the registered singleton object, or {@code null} if none found
     */
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }
复制代码

创建Bean的时候进行增强

public interface ObjectFactory<T> {
​
    /**
     * Return an instance (possibly shared or independent)
     * of the object managed by this factory.
     * @return the resulting instance
     * @throws BeansException in case of creation errors
     */
    T getObject() throws BeansException;
​
}
复制代码

getObject 的时候会触发AbstractAutowireCapableBeanFactory类createBean -> doCreateBean方法

创建Bean的时候会判断是否支持三级缓存,支持会提前暴露对象

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
复制代码

getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

    /**
     * Obtain a reference for early access to the specified bean,
     * typically for the purpose of resolving a circular reference.
     * @param beanName the name of the bean (for error handling purposes)
     * @param mbd the merged bean definition for the bean
     * @param bean the raw bean instance
     * @return the object to expose as bean reference
     */
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
        return exposedObject;
    }
​
复制代码

此外创建对象时,还会进行检查(第二级缓存 和 原始对象 是否相等)

    if (earlySingletonExposure) {
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {
                        throw new BeanCurrentlyInCreationException(beanName,
                                "Bean with name '" + beanName + "' has been injected into other beans [" +
                                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                "] in its raw version as part of a circular reference, but has eventually been " +
                                "wrapped. This means that said other beans do not use the final version of the " +
                                "bean. This is often the result of over-eager type matching - consider using " +
                                "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }
复制代码

5. 为什么使用@Async的循环依赖不能行

@Async本质上也是通过代理模式进行增强

就是在检查的时候注入不成功,说明在两个代理对象不一致

这就是过早注入的弊端,在使用异步注解的时候,spring的默认实现是生成该类的代理对象,通过代理对象调用异步方法。也就是说这个代理对象不是单例的,一个先前已经被注入的代理,一个是刚生成的代理,而Spring三级缓存对Bean的属性注入要求都是单例的对象,这就造成了错误。

所以说,我们解决这个问题,只能去破坏循环依赖,或者优化类的加载顺序,如果先创建的是不加@Async的对象,则不会报错

6. 解决方案

  • 重新设计,避免循环依赖,从根源上解决循环依赖问题
  • 使用@Lazy注解,延迟加载
  • 使用@DependsOn注解,指定加载的先后关系
  • 修改文件名称,改变循环依赖类的加载顺序(Spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载)

需要自行解决的循环依赖:

  • 多例循环依赖(不是单例模式)
  • 构造器循环依赖
  • @DependsOn产生的循环依赖
  • @Async的循环依赖
  • ...
基于STM32的坐姿矫正与久坐提醒系统 | 保姆式教程 开源可复刻 本项目基于STM32F103C8T6主控,搭配MPU6050六轴姿态传感器、HC-SR04超声波测距模块,完整实现了坐姿矫正与久坐提醒系统的全流程开发,从多传感器数据采集、数字滤波、姿态解算、坐姿判断到多模式提醒,全流程无跳步保姆式拆解,配套逐行注释可直接编译的开源代码,新手跟着步骤一步步操作,就能顺利完成项目复刻 阅读详情

相关推荐

第八篇 永磁同步电机控制-MTPA、MTPV

MTPA 的全称是 Maximum Torque Per Ampere,即 最大转矩电流比控制。核心思想:在输出特定转矩时,通过控制定子电流的幅值和相位(即d轴和q轴电流的分量),使得所需要的定子电流幅值最小。通俗理解:就是用“最省电”的方式产生所需要的力矩。就像开车时,找到一个最合适的油门和档位配合,使得在输出相同动力时,油耗最低。

深耕汽车电子:AUTOSAR、电机控制,助力数字汽车 1868

what?spring已经解决循环依赖了,为啥还报循环依赖错误?

前言 spring中的循环依赖及三层map解决方案,八股文中重灾区,强如小伙伴们或许一字一句倒背如流了,当年的我也是如此,然而现实狠狠给了我一巴掌,报错虽迟但到 报错一: The dependencies of some of the beans in the application context form a cycle 报错二: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating

show me the code 7900

Halcon深度图转点云实战:从XYZ映射到3D可视化全解析

本文详细解析了如何使用Halcon将深度图转换为点云图,涵盖从XYZ映射到3D可视化的全流程。通过实战案例和代码示例,介绍了深度图预处理、xyz_to_object_model_3d核心转换及3D可视化技巧,适用于工业检测、三维重建等场景。

stem5的博客 766

Spring源码分析之如何解决循环依赖

spring-ioc中循环依赖的问题,也算是高频的面试问题了,今天跟大家一起来总结一下spring-ioc中是如何解决循环依赖的,相信大家是可以从这篇文章中彻底理解spring容器如何帮我们解决循环依赖,为了更好的理解spring-ioc如何解决循环依赖,大家可以先简单的了解spring-ioc中bean实例化的整个时序图。 一、spring-ioc解决循环依赖的位置 红色的标注框的地方,表示解决循环依赖的重点逻辑,后面会跟大家一起详细阅读,这里大家可以先有个印象 二、spring-ioc解决循环

每天叫醒自己的不是闹铃 是娟 是梦 是理想。 746

物联网技术-第2章-物联网技术架构

• 物联网具有很强的异构性• 为实现异构设备之间的互联、互通与互操作,物联网需要以一个开放的分层的可扩展的为框架。

m0_73804764的博客 2976

Camera ITS测试

一、手机端设置: 将 CtsVerifier.apk 安装到设备上, 连接上linux主机。 二、主机端设置: 基础:CTS测试运行环境 然后,主机安装python2.7,及numpy/scipy/matplotlib/opencv 栈和 python 成像库。按照下面的命令依次安装: sudo apt­-get install python-­numpy sudo apt-­g...

wangubuntu的专栏 7784

spring小结---bean循环依赖

// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() &&a...

xiaoza7的博客 417

电源设计之路-Buck电源环路设计(Type IIIA&Type IIIB)

在对Buck 环路补偿Type II拓扑进行仿真测试后,对瞬态波形以及伯德图已经有感官上的认知;然后再进一步对Type IIIA & IIIB进一步的学习;进一步了解Cout电容量和ESR对环路补偿的影响;认识到一个电源,不是参考Datasheet上的参数进行选值就行;更需要根据实际情况,功率电感值、输出电容值以及不同电容会有不一样的ESR,这些都会对环路参数产生一定的影响

2503_92839756的博客 751

Spring大白话--三级缓存解决循环依赖问题

在使用Spring 开发过程中,我们需要对定义后的bean 通过构造方法,或者bean 注入的方式注入到某个类中进而使用改bean 对应的方法,在此过程中就会出现一个类中注入了n多个bean,几个类中bean 相互注入,出现 A 依赖B,B依赖C,C又依赖A/B 这种循环情况的出现。提示:以下是本篇文章正文内容,下面案例可供参考。

拽着尾巴的鱼儿的博客 4909

Coze 打通飞书多维表格,实现数据增删改查操作实战详解

基于Coze 打通飞书多维表实现数据录入系统实战详解

congge 5779

Mac版 Android Studio自带模拟器无法联网解决方案

前言 作为一枚安卓开发者,免不了使用模拟器来开发调试(毕竟它比手机方便太多)。但是因为适配需要,我们会更多的使用真机开发调试,那么当模拟器遇到这样那样的问题,我们往往会无从下手,其中最烦的就是网络问题。 我最近图方便,又重新用起了模拟器,但是发现联网失败,用模拟器内置的浏览器也打不开网页,于是百度上一通找,毫无效果,各种说用cmd命令行设置模拟器dns的都没用,显示没有什么dns文件啥的,后来...

陈万洲的专栏 1万+

不是说Spring三级缓存可以解决循环依赖吗?怎么我写的就GG了(1)

总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!” />总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习。

2401_84010107的博客 942

时空智友企业流程化管控系统文件上传漏洞

时空智友企业流程化管控系统是一个功能丰富、灵活可定制的企业管理工具。通过该系统,企业能够实现流程的自动化、协同的提升、数据的洞察和决策的优化,从而提高工作效率、管理水平和企业竞争力。时空智友企业流程化管控系统 formservice接口处存在任意文件上传漏洞,未经认证的攻击者可通过此接口上传后门文件,最终可导致服务器失陷。

holyxp的博客 1030

Spring为何需要三级缓存解决循环依赖,而不是二级缓存?

「关注“石杉的架构笔记”,大厂架构经验倾囊相授」儒猿技术团队最新出品《大型企业级分布式订单系统项目实战》(点击查看详情)文章来源:http://u6.gg/k53gg在使用 Spri...

石杉的架构笔记 1724

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

为什么Spring需要三级缓存解决循环依赖,而不是二级缓存?

点击上方“服务端思维”,选择“设为星标”回复”669“获取独家整理的精选资料集回复”加群“加入全国服务端高端社群「后端圈」作者 | 半分、笑出品|http://u6.gg/kj00k在...

梁桂钊的博客 270

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Spring解决循环依赖

spring三级缓存

perfectyw的博客 890

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Spring - 循环依赖

1、什么是循环依赖 很简单,看下方的代码就知晓了 @Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; } //或者自己依赖自己 @Service public class A { @Autowired private A a; } 上面这两种方式都是循环依赖,应该很好理解,当然也可以是三个 Bean

。 6万+

不知道Spring如何解决循环依赖问题?看这一篇

之前我们已经学习过SpringBean生命周期的相关概念,在创建Bean对象的时候可能会出现一个循环依赖问题。本章主要介绍了循环依赖的基本概念以及解决办法。现在有个A对象,它的属性是B对象,而B对象的属性也是A对象,这就是循环依赖。 说白了就是A依赖B,而B又依赖A。(我们主要分析两个Bean之间的依赖)这种循环依赖可能会产生哪些问题? 例如:A 要依赖 B,发现 B 还没创建。于是开始创建 B ,创建的过程发现 B 要依赖 A, 而 A 还没创建好呀,因为它要等 B 创建好,就这样 它们俩就搁这卡 bu

qq_52173163的博客 697

Spring循环依赖,我本来都不想写的,但网上好多错误观点

今天我们来盘 Spring 的经典面试题循环依赖。 这的面试题很经典,网上相应的文章很多,但是我还是想写一下,因为有些文章的观点在我看来是错的。 比如有构造器就无法解决循环依赖? 一定要三级缓存才能解决循环依赖? 到底为什么要三级缓存? 好了,话不多少,我们来盘一盘。 什么是循环依赖 很简单,看下方的代码就知晓了 @Service public class A { @Autowired private B b; } @Service public c

HongYu012的博客 384
上一篇: 手把手教你玩 MySQL 删库不跑路,直接把 MySQL 的 binlog 玩溜
下一篇: 面试造火箭系列,栽在了cglib和jdk动态代理
superjava_
博客等级 码龄5年 254粉丝 81原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值