Spring中的循环依赖

【Spring源码:循环依赖】一文弄懂Spring循环依赖 1. 什么是循坏依赖 很简单,其实就是互相依赖对方,比如,有一个A对象依赖了B对象,B对象又依赖了A对象。 // A依赖了B public class A{ private B b; } // B依赖了A public class B{ private A a; } 但是,在我们普通的java开发中,循坏依赖会出现问题吗? 如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖注入是很正常的事情。 比如: 这样,A,B就互相依赖上了。 但是,在Spring 阅读详情

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring中循环依赖场景有: 
(1)构造器的循环依赖 
(2)field属性的循环依赖。

思考:在编码的过程中,有时候会存在这样的诉求,那这时候,程序应该先创建哪个对象?

Spring怎么解决循环依赖

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。

那spring一个对象产生需要以下几个步骤

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:调用spring xml中的init 方法。

/**
     * 实际创建指定的bean。 此时,预创建处理已经发生,
     * 例如 检查{@code postProcessBeforeInstantiation}回调。
     * 区分默认bean实例化、使用工厂方法和自动装配构造函数。
     */
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        .....
        
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        .....
        // 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.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            //添加到三级缓存中
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
 
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //填充依赖的bean实例。
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                //调用spring xml中的init 方法。
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        ......
        return exposedObject;
    }

其实我们简单的思考一下就发现,出现循环依赖的问题主要在 (1)和 (2)两个步骤,也就是也就是(1)中构造器循环依赖和(2)中field循环依赖。

注:createBeanInstance(...)该步骤会调用构造方法

/**
     *使用适当的实例化策略为指定的bean创建一个新实例:
     * 工厂方法,构造函数自动装配或简单实例化。
     * @return BeanWrapper for the new instance
     */
    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
		
        .......
        // Need to determine the constructor...(确定构造函数)
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
            return autowireConstructor(beanName, mbd, ctors, args);
        }
 
        // No special handling: simply use no-arg constructor.(使用默认无参构造器,
        //编程时候要求尽量保留无参构造器,因为你不知道哪个框架在哪会用到)
        return instantiateBean(beanName, mbd);
    }

如何解决?

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

/** Cache of singleton objects: bean name --> bean instance */
    //单例对象的cache
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
 
    /** Cache of singleton factories: bean name --> ObjectFactory */
    //单例对象工厂的cache
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
    /** Cache of early singleton objects: bean name --> bean instance */
    //提前暴光的单例对象的Cache
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

步骤:

1.Spring首先从一级缓存singletonObjects中获取。

2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。

3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.

4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

/**
     * 返回指定名称(原始)单例对象。
     * 检查已经实例化的单例,也允许对当前创建的单例对象的早期引用(用于解决循环依赖)
     * 
     * @return the registered singleton object, or {@code null} if none found
     */
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //Spring首先从一级缓存singletonObjects中获取。
        Object singletonObject = this.singletonObjects.get(beanName);
 
        //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化
        //完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依
        //赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
 
                //尝试从二级缓存earlySingletonObjects中获取。
                singletonObject = this.earlySingletonObjects.get(beanName);
 
                //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
                if (singletonObject == null && allowEarlyReference) {
                    //尝试从三级缓存singletonFactory.getObject()(三级缓存)获取
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
 
                    //如果从三级缓存中获取到
                    // 则从singletonFactories中移除,并放入earlySingletonObjects中。
                    // 其实也就是从三级缓存移动到了二级缓存。
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

从三级缓存的分析来看,Spring解决循环依赖的诀窍就在于singletonFactories这个滴三级cache。这个cache的类型是ObjectFactory,定义如下:

/**
 * 定义一个可以返回对象实例的工厂
 * @param <T>
 */
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

接口在这个方法中被引用,该接口在doCreateBean(...) 方法中 实例化步骤进行了调用

/**
     *  添加一个构建指定单例对象的单例工厂
     *  紧急注册单例对象,用于解决解决循环依赖问题
     * To be called for eager registration of singletons, e.g. to be able to
     * resolve circular references.
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

这里就是解决循环依赖的关键,这段代码发生在doCreateBean(...) 方法中 createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。(简单来说,就是spring创造了一个 循环依赖的结束点标识)

怎么样的循环依赖无法处理?
(1)因为加入singletonFactories三级缓存的前提是执行了构造器来创建半成品的对象,所以构造器的循环依赖没法解决。因此,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!

(2)spring不支持原型(prototype)bean属性注入循环依赖,不同于构造器注入循环依赖会在创建spring容器context时报错,它会在用户执行代码如context.getBean()时抛出异常。因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。

因为spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean。spring容器在获取prototype类型的bean时,如果因为环的存在,检测到当前线程已经正在处理该bean时,就会抛出异常。核心代码

public abstract class AbstractBeanFactory{
    /** Names of beans that are currently in creation */
    private final ThreadLocal<Object> prototypesCurrentlyInCreation =
            new NamedThreadLocal<>("Prototype beans currently in creation");
            
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        // 如果beanName已经存在于正在处理中的prototype类型的bean集中,后面会抛出异常
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
}

【Spring】Spring 解决循环依赖的 3 种方式 原文:http://www.javastack.cn/article/2020/spring-cycle-depends-on-three-ways/ 循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。 下面说一下Spring是如果解决循环依赖的。 第一种:构造器参数循环依赖 Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。 因此如果 阅读详情

相关推荐

SolidWorks有限元分析流程

对于很多专业,在进行设计完成后,为了缩短开发周期,往往需要配合仿真结果进行,机械行业更是如此。平时大家多采用专业有限元仿真软件进行仿真分析,如ANSYS;不过SolidWorks软件因为制图简单,同时存在专用的有限元仿真模块Simulation,对于初学者往往更加适用,下面简单介绍下SolidWorks Simulation仿真流程。 1. 分析流程 关键步骤如下: **(1)创建算例;**对模型的每次分析都是一个算例,一个模型可以包含多个算例。 **(2)应用材料;**指定模型的材料属性,如铝合金、钢材等

DYH_BK的博客 3万+

Spring 循环依赖解决方案

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。结论先行:1.构造器循环依赖----初始化失败2.field属性注入循环依赖----初始化成功3.field属性注入循环依赖(prototype)----初始化失败现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。

sinat_34814635的博客 2万+

元数据管理:DataHub和OpenMetadata对比分析

DataHub和OpenMetadata都试图解决数据编目,搜索,发现,治理和质量方面的相同问题。这两种工具都是出于为需要支持大量数据源、团队和用例的大型组织解决这些问题而诞生的。尽管这些工具在发布历史和成熟度方面略有不同,但它们的功能存在显着重叠。

weixin_38030820的博客 6633

【Spring】Spring的循环依赖以及解决方案

​ Spring循环依赖指的是两个或多个Bean之间相互依赖,形成一个环状依赖的情况。通俗的说,就是A依赖B,B依赖C,C依赖A,这样就形成了一个循环依赖的环。​ Spring循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常。因此,Spring循环依赖是一种需要尽量避免的情况。

长夜烛火的博客 7995

2023年5月青少年软件编程Python等级考试四级真题(含答案和解析)

电子学会,2023年5月,青少年软件编程,Python等级考试,四级真题(含答案和解析)

m0_37554648的博客 904

5、自动化代码结构与运维系统的优化

本文探讨了自动化代码结构与运维系统的优化方法,涵盖审批流程的模块化设计、日志记录与工单系统的集成、通知方式的选择、错误处理的策略以及持续改进的实践。同时,深入分析了运维盲目性问题,通过故障案例揭示了关键指标缺失和日志未充分利用的弊端,提出了开发与运维角色转变的必要性。文章还介绍了如何生成有用的系统指标、建立良好的日志习惯以及实施日志聚合,以提升系统的可观测性和可靠性。最终通过总结和流程图展示了优化措施的核心要点,为构建高效、可靠的运维体系提供了指导。

pp12345的博客 71

Spring 如何解决循环依赖问题

在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的。 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘。 本文主要针对这个问题,从源码的角度对其实现原理进行讲解。 1、过程演示 关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。 在Spring中,对象的实例化是通过反射实现的,而

CRMEB小程序商城的博客 2万+

树莓派 RP2350B 开发环境搭建

本文介绍了树莓派RP2350B开发环境的搭建过程。

张一西的博客 286

Spring如何解决循环依赖

烂大街的Spring循环依赖该如何回答? 什么是循环依赖? 从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身。体现到代码层次就是这个样子 @Component public class A { // A中注入了B @Autowired private B b; } --- @Component public class B { // B中注入了A @Autowired private A a; } --- // 自己依赖自己 @Component public c

意田天的博客 2万+

【微服务】Spring Aop原理深入解析

spring原理深度解析

congge 1万+

X电容与Y电容,看这一篇就够了

本文详细介绍了X电容与Y电容的定义、作用、位置、分类,以及它们在实际应用中的容值选取和计算方法。通过汽车电子应用和X电容放电电路的实例,帮助读者深入理解这两种安规电容的重要性。文章还探讨了电容放电电阻的计算,确保符合安全规范。

让你爱上电路设计 7017

spring 如何解决循环依赖

spring 如何解决循环依赖

快乐的小三菊的博客 3940

1、深入解析OTA/gm - C滤波器架构与设计

本文深入探讨了OTA/gm-C滤波器的架构与设计方法,涵盖了从基本构建模块到高阶滤波器实现的多种技术。重点分析了双积分器环路结构(如Tow-Thomas和KHN滤波器)的工作原理、性能特点及OTA非理想特性对滤波器的影响,并介绍了降低寄生效应、提升动态范围的方法。文章还讨论了基于LC梯形仿真、多环反馈和电流模式的gm-C滤波器设计,以及自动调谐技术在频率稳定性中的应用。最后总结了不同类型gm-C滤波器的优缺点及其适用场景,为高频集成连续时间滤波器的设计提供了全面的技术参考。

fanta的博客 185

【Spring】Spring的循环依赖及解决方案

在Spring框架中,循环依赖是一个非常常见的问题。当两个或多个bean之间相互依赖时,就会产生循环依赖。这种情况可能会导致程序无限循环,从而导致性能问题和程序崩溃。Spring框架通过检测循环依赖并采取相应措施来解决这个问题。例如,使用懒加载的方式来避免循环依赖。但是,有时候我们的代码中可能会有一些错误,导致循环依赖的产生。在这篇博客中,我们将深入探讨Spring循环依赖的问题。我们将讨论如何产生循环依赖,以及如何避免和解决循环依赖。我们还将深入探讨Spring框架如何检测和解决循环依赖。

qq_46096136的博客 9466

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Spring中的循环依赖问题

浅谈spring中的循环依赖问题

AG.『Feng』的博客 3792

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Spring之循环依赖

Spring在Bean的实例化过程中,提供了对循环依赖的解决方案,但是这部分代码非常的生涩难懂,今天,我们就从一个Spring的源码来看看这个循环依赖到底是何方神圣! 首先Spring中的循环依赖到底是什么呢? 循环依赖就是循环引用,就是在类A中引用了类B,而类B中引用了类C,而类C中又引用了类A,如图: 图一 在图一中,我们可以看到A中存在属性B,B中存在属性C,而C中有又存在属...

Taylar_where的博客 9429

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Spring循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 Bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。结果:需要注意的是本例中启动时是不会报错的(因为非单例Bean默认不会初始化,而是使用时才会初始化),所以很简单咱们只需要手动。对于构造器注入出现缓存依赖,Spring 是无法解决的,因为当前 Bean 还未实例化,无法提前暴露对象,所以只能抛出异常。)​:存储Bean的ObjectFactory,用于创建早期的Bean实例。

qq_40463629的博客 2145

Spring — 循环依赖

读完这篇文章你将会收获到 Spring循环依赖可以分为哪两种 Spring如何解决 setter循环依赖 Spring为何是三级缓存 , 二级不行 ? Spring为啥不能解决构造器循环依赖 概述 循环依赖就是循环引用,两个或以上的 bean相互持有对方。比如说 beanA引用 beanB, beanB引用 beanC, beanC引用 beanA, 它们之间的引用关系构成一个环。 Spring 如何解决循环依赖 Spring中的循环依赖包括 构造器循环依赖 setter循环依赖 构造器的依赖 S

CoderLi 1422

Spring 是怎么解决循环依赖的

循环依赖,简单来说就是多个 Bean 之间相互依赖,形成一个闭环。例如,Bean A 依赖 Bean B,Bean B 又依赖 Bean C,而 Bean C 反过来又依赖 Bean A,这样就构成了循环依赖。在 Spring 容器创建 Bean 的过程中,如果遇到循环依赖,若不进行特殊处理,就会导致创建 Bean 的过程陷入死循环,无法成功初始化 Bean。​根据依赖注入方式的不同,循环依赖主要分为三种类型:​构造器循环依赖:通过构造函数进行依赖注入时产生的循环依赖。

qq_74181189的博客 1134

Spring Bean 循环依赖详解

时序图帮助理解Spring Bean 循环依赖

分享Java,Rust相关技术 1505

Spring如何循环依赖

一、什么是循环依赖 循环依赖指的是2个或2个以上的bean互相持有对方,从而形成闭环的依赖场景。例如A依赖B,B依赖C,而C又依赖A,这种情况就叫做循环依赖。 二、循环依赖的场景 1、构造器的循环依赖 2、setter循环循环依赖 三、Spring Bean的创建过程 源码见org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) @Override public Object getBea

weixin_41951205的博客 1420

spring循环依赖

spring循环依赖主要有三种: 单例引用类型循环依赖(属性):允许 构造器的循环依赖:不允许 多例循环依赖:不允许 单例引用类型循环依赖(属性) package com.spring.bean; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Compo...

zhouyi_YYDS的博客 4988

Spring 循环依赖

先从一级缓存singletonObjects中去获取。(如果获取到就直接return)如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。

loushaoqi的博客 1256
上一篇: 插入数据时遇到主键重复问题怎么办?——insert into数据库技巧 (insert into主键重复数据库)
下一篇: RabbitMQ如何防止消息丢失及重复消费
涵冰...
博客等级 码龄4年 2438粉丝 243原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涵冰...

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值