Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring中的循环依赖解决方案

Spring中的循环依赖解决方案

作者头像
naget
发布于 2019-07-03 07:48:16
发布于 2019-07-03 07:48:16
2.1K00
代码可运行
举报
文章被收录于专栏:VegoutVegout
运行总次数:0
代码可运行

Spring中的循环依赖

循环依赖:循环依赖就是循环引用,就是两个或多个bean互相之间持有对方。比如beanA引用beanB,beanB引用beanA,当我们实例化beanA的时候发现beanB作为beanA的成员对象出现了,那么我们就可能在实例化beanA的中间需要先实例化beanB,然后完成beanB的实例化之后,才能完成beanA的实例化,可惜的是beanB中也引用了beanA,在实例化beanB过程中又需要实例化beanA,而beanA正在进行实例化,但完成beanA的实例化的条件是beanB实例化完成,完成beanB实例化的条件是完成beanA的实例化,于是他们最终反映为一个环状依赖,难以完成实例化。

Spring如何解决循环依赖

spring中循环依赖有三种情况: 1、构造器注入形成的循环依赖。也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成舒适化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。 2、setter注入构成的循环依赖。beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖,也是今天下文讨论的重点。 3、prototype作用域bean的循环依赖。这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。

下面主要说明第二种情况中循环依赖的解决方案

步骤一:beanA进行初始化,并且将自己进行初始化的状态记录下来,并提前向外暴露一个单例工程方法,从而使其他bean能引用到该bean(可能读完这一句,您仍然心存疑惑,没关系,继续往下读) 步骤二:beanA中有beanB的依赖,于是开始初始化beanB。 步骤三:初始化beanB的过程中又发现beanB依赖了beanA,于是又进行beanA的初始化,这时发现beanA已经在进行初始化了,程序发现了存在的循环依赖,然后通过步骤一中暴露的单例工程方法拿到beanA的引用(注意,此时的beanA只是完成了构造函数的注入但为完成其他步骤),从而beanB拿到beanA的引用,完成注入,完成了初始化,如此beanB的引用也就可以被beanA拿到,从而beanA也就完成了初始化。

spring进行bean的加载的时候,首先进行bean的初始化(调用构造函数),然后进行属性填充。在这两步中间,spring对bean进行了一次状态的记录,也就是说spring会把指向只完成了构造函数初始化的bean的引用通过一个变量记录下来,明白这一点对之后的源码理解至关重要。

源码角度观看循环依赖的解决步骤

步骤一中首先进行beanA的创建

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
                    if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });

进入getSingleton中,spirng会记录当前beanA正在创建中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }

并且将注册一个工厂方法来解决循环依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        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, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

主要就是addSingletonFactory,这句就完成了工厂方法的注册,这个方法可以返回一个只完成了构造函数初始化的beanA,也许大家想知道他是如何返回的,我们进入getEarlyBeanReference方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

可以看到这个方法中,除了对后处理器的调用,没有进行任何动作,而是直接返回了我们参数传入的bean,那么这个bean是哪来的呢?其实在这之前,spring先调用了beanA的构造函数,并拿到了只完成了构造函数初始化的一个实例,并把他记录了下来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = instanceWrapper.getWrappedInstance();

这就是这个bean的由来。然后当我们进行到步骤三的时候,就会检查是否允许循环依赖(即使是Singleton类型的bean也可以通过参数设置,禁止循环依赖),如果允许的话,就会通过这个工厂方法拿到beanA的引用。从而完成beanA和beanB的加载。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        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) {
              这里:     singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

一个不甚恰当的循环依赖类比

看比尔盖茨的女婿和世界银行部总裁这两个身份如何解决循环依赖

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vegout 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
烂了大街的 Spring 循环依赖问题,你觉得自己会了吗
初学 Spring 的时候,我们就知道 IOC,控制反转么,它将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,不需要我们手动去各种 new XXX。
海星
2020/09/09
6950
【彻底搞懂】Spring之三级缓存解决循环依赖问题
spring解决循环依赖是通过对象的实例化和初始化分开的步骤来实现的,如果是构造函数注入的话,对象实例化就卡住了
西柚dzh
2022/06/09
3.8K2
【彻底搞懂】Spring之三级缓存解决循环依赖问题
Spring IOC 容器源码分析 - 循环依赖的解决办法
本文,我们来看一下 Spring 是如何解决循环依赖问题的。在本篇文章中,我会首先向大家介绍一下什么是循环依赖。然后,进入源码分析阶段。为了更好的说明 Spring 解决循环依赖的办法,我将会从获取 bean 的方法getBean(String)开始,把整个调用过程梳理一遍。梳理完后,再来详细分析源码。通过这几步的讲解,希望让大家能够弄懂什么是循环依赖,以及如何解循环依赖。
田小波
2018/06/08
1.8K0
Spring IOC 容器源码分析 - 循环依赖的解决办法
Spring源码解析(五)Spring 加载bean 依赖注入
至此,我们分析过后,可以简单的归纳一下Spring IOC容器的过程 一、Resource定位过程 这个Resource定位指的的是BeanDefinition的资源定位,他由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口,对于这些BeanDefinition的存在形式,相信大家都不会感到陌生,比如,在类路径中的Bean定义信息可以使用FileSystemResouce来进行抽象;在类路径中的Bean定义信息可以使用前面提到的ClassPathResource来使用,等待;这个定位过程类似于容器寻找数据的过程,就像用水桶装水先要把水找到一样;
石臻臻的杂货铺[同名公众号]
2021/07/14
6130
Spring循环依赖三级缓存是否可以减少为二级缓存?
我们都知道Spring通过三级缓存来解决循环依赖的问题,那么是不是必须是三级缓存?二级缓存不能解决吗?
程序员白楠楠
2021/01/29
9350
Spring源码解析(十二)Spring扩展接口SmartInstantiationAwareBeanPostProcessor解析
之前我们分析了 InstantiationAwareBeanPostProcessor、BeanPostProcessor、今天来分析一下SmartInstantiationAwareBeanPostProcessor的用法;
石臻臻的杂货铺[同名公众号]
2021/07/14
1.9K0
一张图彻底搞懂Spring循环依赖
BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类。这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖。同理,再如下图的情况:
Tom弹架构
2021/10/29
7690
一篇文章带你读懂Spring如何解决循环依赖!
当多个Bean相互依赖时则构成了循环依赖,例如A,B两个Bean。其中A中存在属性B,B中存在属性A,当Spring在实例化A时发现A中存在属性B,就去实例化B,实例化B时又发现存在属性A,一直在循环注入依赖,导致循环依赖问题出现。
Java程序猿
2021/06/25
4180
Spring是如何解决循环依赖的?
A对象,它的属性是B对象,而B对象的属性也是A对象,说白了就是A依赖B,而B又依赖A
Blue_007
2023/10/21
3020
Spring是如何解决循环依赖的?
Spring源码解析(五):循环依赖
在完成Bean的实例化后,属性注入之前Spring将Bean包装成一个工厂对象添加进了三级缓存中,对应源码如下:
冬天vs不冷
2025/01/21
3130
Spring源码解析(五):循环依赖
Spring源码解析(四):单例bean的创建流程
冬天vs不冷
2025/01/21
1630
Spring源码解析(四):单例bean的创建流程
Spring中的循环依赖解决详解
原 https://www.cnblogs.com/leeego-123/p/12165278.html
猿芯
2021/03/22
3990
Spring 源码学习 16:单例 Bean 创建
在 finishBeanFactoryInitialization 中介绍了创建 Bean 的流程大概流程,这里进入单例 Bean 的创建过程。
程序员小航
2021/01/21
4710
Spring如何解决循环依赖的
就是我们有两个服务,A服务,B服务,然后我们在A里注入了B,然后在B里注入了A,这就是循环依赖了,这种情况如果我们不解决的话,那就会出现一个相互依赖注入的死循环。
名字是乱打的
2021/12/24
1.1K0
Spring如何解决循环依赖的
spring解决循环依赖
spring 单例对象的实例化、初始化过程是在doCreateBean中(之前仅仅是注册好了BeanDefenition), 大概分为三步:
leobhao
2022/06/28
5440
spring源码分析之如何解决循环依赖
spring-ioc中循环依赖的问题,也算是高频的面试问题了,今天跟大家一起来总结一下spring-ioc中是如何解决循环依赖的,相信大家是可以从这篇文章中彻底理解spring容器如何帮我们解决循环依赖,为了更好的理解spring-ioc如何解决循环依赖,大家可以先简单的了解spring-ioc中bean实例化的整个时序图。
全栈程序员站长
2022/07/04
4430
spring源码分析之如何解决循环依赖
一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】
循环依赖:就是N个类循环(嵌套)引用。 通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):
YourBatman
2019/09/03
52.9K12
一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】
Spring IOC原理补充(循环依赖、Bean作用域等)
通过之前的几篇文章将Spring基于XML配置的IOC原理分析完成,但其中还有一些比较重要的细节没有分析总结,比如循环依赖的解决、作用域的实现原理、BeanPostProcessor的执行时机以及SpringBoot零配置实现原理(@ComponentScan、@Import、@ImportSource、@Bean注解的使用和解析)等等。下面就先来看看循环依赖是怎么解决的,在此之前一定要熟悉整个Bean的实例化过程,本篇只会贴出关键性代码。
夜勿语
2020/09/07
6020
这么回答【循环依赖】助力轻松拿下阿里P6
  上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。
用户4919348
2022/10/04
2210
这么回答【循环依赖】助力轻松拿下阿里P6
Spring的循环依赖和三级缓存
(2)如果您不了解Spring Bean的声明周期,那么您可以看一下文章(Bean的生命周期_CBeann的博客-CSDN博客)或者百度其它文章,然后在回来看该文章,否则个人感觉应该看不懂
CBeann
2023/12/25
2270
Spring的循环依赖和三级缓存
推荐阅读
相关推荐
烂了大街的 Spring 循环依赖问题,你觉得自己会了吗
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验