我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java Spring 循环依赖解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java Spring 循环依赖解析

1、常见问题

  • 你解释一下 spring 中的三级缓存?
  • 三级缓存分别是什么?三个 Map 有什么异同?
  • 什么是循环依赖?请谈谈?你看过 spring 的源码吗?一般我们说的是 spring  容器是什么?
  • 多例的情况下,循环依赖问题为什么无法解决?

2、什么是循环依赖?

多个 bean 之间相互依赖,形成闭环。比如:A 依赖于 B, B 依赖于 C , C 依赖于 A

示例代码:

public class T1 {

  class A {
    B b;
  }
  class B {
    C c;
  }
  class C {
    A a; 
  }
}

比如:A 依赖于 B, B 依赖于 C , C 依赖于 A
通常来说,如果问 Spring 容器内部如何解决循环依赖,一定是指默认的单例 Bean 中, 属性相互引用的场景。

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private C c;
}
 
@Component
public class C {
    @Autowired
    private A a;
}

3、循环依赖说明

官方说明:

参考官方说

构造方法注入:不支持循环依赖。
我们 AB 循环依赖问题。只要 A 的方式是 setter 且 singleton
就不会有循环依赖问题。

4、BeanCurrentlyInCreationException

循环依赖异常的定义如下所示,如果出现循环依赖,我们在启动/运行过程中会报这个错误。

5、依赖注入的两种方式

方式一:构造器方式注入依赖

@Component
public class ServiceA{

    private ServiceB serviceB;
        
    public ServiceA(ServiceB serverB) {
       this.serivceB = serviceB;
    }     
}

@Component
public class ServiceB{

    private ServiceA serviceA;
        
    public ServiceB(ServiceA serviceA) {
       this.serviceA = serviceA;
    }     
}

构造器循环依赖是无法解决的,你想让构造器注入支持循环依赖,是不可能的。

方式二:以 set 方式注入依赖

@Component
public class ServiceA{

    private ServiceB serviceB;
        
    public setServiceB(ServiceB serverB) {
       this.serivceB = serviceB;
    }     
}

@Component
public class ServiceB{

    private ServiceA serviceA;
        
    public setServiceB(ServiceA serviceA) {
       this.serviceA = serviceA;
    }     
}

案例演示(基于 Spring 容器的循环依赖)

普通的 Java 基础编码A  类、B 类

@Data
public class A{
  private B b;
  
  public A() {
     System.out.println("----- A create success");
  }
}

@Data
public class B{
  private a a;

  public B() {
     System.out.println("------ B create success");
  }    
}

循环依赖解决

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);

基于 Spring 容器的循环依赖

  • 默认的单例(singleton)的场景是支持循环依赖的,不报错
  • 原型(prototype)的场景是不支持循环依赖的, 会报错

代码演示:

循环依赖代码:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A {

    @Autowired
    private B b;
}

@Component
public class B {

    @Autowired
    private A a;
}

默认单例,修改为原型

// 增加注解
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

一段测试程序

class BTest {


    @Configuration
    @Import({A.class, B.class})
    public static class TestConfig {

    }

    @Test
    public void currentlyincreation() {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestConfig.class);
        // 这里要获取 bean 一下,如果不去主动获取,可能是由于惰性加载没有执行,不会报错
        A a = applicationContext.getBean(A.class);
        System.out.println(a);
    }
}

循环依赖异常

6、Spring 三级缓存介绍和循环依赖解决过程

三级缓存介绍

核心类: DefaultSingletonBeanRegistry

第一级缓存:(也叫单例池)singletonObjects: 存放已经经历完整生命周期的 Bean 对象
第二级缓存:earlySingletonObjects: 存放早起暴露出来的 Bean 对象, Bean 的生命周期未结束(属性还未填充完成)
第三级缓存:Map<String, ObjectFactory<?>> singletonFactories可以存放 Bean 工厂。
只有单例 Bean 会通过三级缓存提前暴露出来解决循环依赖问题,而非单例的 bean, 每次从容器获取都是新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会放到三级缓存中。

实例化/初始化定义

实例化/初始化

1、实例化:内存中申请一块内存空间;(比如:租赁好好房子,自己的家具沙发,床还没有搬进去。)
2、初始化属性填充:完成各种属性的赋值;(比如:装修,家电家具进场)

三级缓存使用过程

3 个 map 的四大方法,总体相关对象

第一层:(也叫单例池)singletonObjects: 存放已经初始化好的 Bean。
第二层:earlySingletonObjects: 存放的是实例化的了, 但是未初始化的 Bean。
第三层:Map<String, ObjectFactory<?>> singletonFactories
存放的是 FactroyBean。假如 A 类实现了 FactoryBean, 那么依赖注入的时候不是 A 类,而是 A类的 FAC天FactoryBean。


private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);


private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);


private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

A/B 两对象在三级缓冲的迁移说明

1、创建 A 过程中需要 B, 于是 A 将自己放入到三级缓存里面,去实例化 B
2、B 实例化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有再查三级缓存,找到了 A 然后把三级缓存里面的这个 A 放入到二级缓存里面,并且删除三级缓存里面的 A。
3、 B 顺利初始化完毕后,将自己放入到一级缓存里面(此时 B 里面的 A 依然是创建中状态)后来接着创建 A, 此时 B 已经创建结束,直接从一级缓存里面拿到 B,然后完成创建,并且将 A 自己放到一级缓存中

ObjectFactory 接口

@FunctionalInterface
public interface ObjectFactory<T> {

    
    T getObject() throws BeansException;

}

DEBUG 断点调试

1、Bean 创建

2、放入三级缓存

3、属性填充(就是做属性注入)

循环依赖解决

整理流程梳理:

1、调用 doGetBean() 方法 , 想要获取 beanA , 于是调用 getSingletion() 方法从缓存中查询 beanA
2、在 getSingletion() 方法中,从一级缓存中查找,没有,返回 null
3、doGetBean() 犯法中获取到 beanA 为 null, 于是就走对应的处理逻辑,调用 getSIngletion() 的重载方法(参数为 ObjecFactory  的)
4、在 getSingletion() 方法中,先将 beanB_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean  方法
5、进入 AbstractAutowireCapableBenaFactory#doCreateBean , 先反射调用构造器创建出 beanA 的实例,然后判断,是否为单例、是否允许提前暴露引用(对于单例一般为 true)、是否正在创建中(即是在第四步的集合中)。判断为 true 则将 beanA 添加到【三级缓存】中。
6、对 beanA 进行属性填充,此时检测到 beanA 依赖 beanB , 于是开始查找 beanB
7、调用 doGetBean 方法,和上面的 beanA 过程一样,到缓存中查找 beanB, 没有则创建,然后给 beanB 填充属性。
8、此时 beanB  依赖于 beanA , 调用 getSingleton() 获取 beanA , 依次从一级、二级、三级缓存中找,此时三级缓存中获取到 beanA 的创建工厂,通过创建工厂获取到 singletonObject, 此时这个 singletonObject 指向的就是上面的 doCreateBean() 方法实例化的 beanA 。
9、这样 beanB 就获取到了 beanA 的依赖,于是 beanB 顺利完成实例话,并将 beanA 从三级缓存移动到二级缓存中。
10、随后 beanA  继续他的属性填充工作,此时也获取到了beanB , beanA 也随之完成了创建,回调 getSingleton() 方法中继续向下执行,将 beanA 从二级缓存中移动到一级缓存中。

7、Spring 循环依赖总结

Spring 创建 Bean 主要分为两个步骤,创建原始 Bean 对象,接着去填充属性和初始化。
每次创建 Bean 之前,我们都会从缓存中查一下有没有该 bean , 因为是单例, 只能有一个
当我们创建 beanA  的原始对象之后,并且把它放入到三级缓存中,接下来就该填充属性了,这个时候发现依赖了 beanB , 就直接去创建 beanB, 同样的流程,创建完 beanB 统筹国内属性时又发现了它依赖了 beanA  又是同样的流程。

不同的是:

这个时候可以在三级缓存只能够查询到刚才放进去的原始对象 beanA , 所以不需要继续创建,用它注入 beanB , 完成 beanB 的创建
既然 beanB 创建好了,所以 beanA 就可以弯沉属性填充的步骤了,接下来执行剩下的逻辑完,完成闭环

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从 singletonObject 获取实例,singletonObject 中的实例都是准备好的 bean 实例
    Object singletonObject = this.singletonObjects.get(beanName);
    // isSingletonCurrentlyInCreation() 判断当前单例 bean 是否正在创建中
    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;
}

Spring 解决循环依赖靠的是 Bean 的“中间态” 这个概念。而这个中间态指的是 已经实例化还没有初始化的状态 ---> 半成品,实例话的过程有是通过构造器创建的,如果 A 还没有创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring 为了解决单例的循环依赖问题,使用了三级缓存:

  • 其中一级缓存为单例池(singletonObjects)
  • 二级缓存为提前曝光对象(earlySingletonObjects)
  • 三级缓存为提前曝光对象工厂(singletonFactories)

假设 A,B 循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了 B ,同样的流程也是实例化后放入三级缓存,接着去填充属性时发现自己依赖 A,这个时候从缓存中查找到早起暴露的 A,没有 AOP 代理的化,直接将 A 原始对象注入 B,完成 B的初始化后,进行属性填充和初始化,这个时候 B完成后,就去完成剩下 A的步骤,如果有 AOP 代理,就会进行 AOP 处理获取代理后的  A,注入 B, 走剩下的流程。

到此这篇关于Java Spring 循环依赖解析的文章就介绍到这了,更多相关Spring 循环依赖解析内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java Spring 循环依赖解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

spring循环依赖策略解析

循环依赖所谓循环依赖就是多个Bean之间依赖关系形成一个闭环,例如A->B->C->...->A 这种情况,当然,最简单的循环依赖就是2个Bean之间互相依赖:A->B(A依赖B), B->A(B依赖A) 。在Spring中,如果A->B,
2023-05-31

分析Spring循环依赖的坑

本篇内容介绍了“分析Spring循环依赖的坑”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 前言这两天工作遇到了一个挺有意思的Sprin
2023-06-16

Java中的Spring循环依赖实例分析

这篇文章主要讲解了“Java中的Spring循环依赖实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中的Spring循环依赖实例分析”吧!什么是循环依赖?很简单,就是A对象依赖
2023-06-30

怎么理解java中Spring循环依赖

这篇文章主要介绍“怎么理解java中Spring循环依赖”,在日常操作中,相信很多人在怎么理解java中Spring循环依赖问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解java中Spring循环依赖
2023-06-16

Spring解决循环依赖的示例分析

这篇文章主要介绍Spring解决循环依赖的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!  这里我们先借用一张图来通过视觉感受一下,看图:    其实,通过上面图片我想你应该能看图说话了,所谓的循环依赖其实就
2023-06-25

怎么理解Spring循环依赖

本篇内容介绍了“怎么理解Spring循环依赖”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!通常来说,如果问Spring内部如何解决循环依赖,
2023-06-16

Spring轻松解决循环依赖

Spring的解决循环依赖是有前置条件的,要解决循环依赖我们首先要了解SpringBean对象的创建过程和依赖注入的方式。依赖注入方式,我之前的博客有所分享,大家可以在看本篇文章之前进行一下小小的回顾
2023-05-16

Spring中的循环依赖

目录 一、什么是循环依赖?二、Bean的生命周期2.1 Spring Bean 的生命周期2.2 Bean 的生成步骤 三、三级缓存3.1三个缓存分别有什么作用 四、思路分析4.1 为什么 Spring 中还需要 singl
2023-08-16

Spring循环依赖原理实例分析

本篇内容介绍了“Spring循环依赖原理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!简介概述@Autowired进行属性注入可以解
2023-07-02

Spring Boot循环依赖怎么解决

本文小编为大家详细介绍“Spring Boot循环依赖怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring Boot循环依赖怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是循环依赖?循
2023-07-05

Spring循环依赖产生与解决

Spring的解决循环依赖是有前置条件的,要解决循环依赖我们首先要了解SpringBean对象的创建过程和依赖注入的方式。依赖注入方式,我之前的博客有所分享,大家可以在看本篇文章之前进行一下小小的回顾
2022-12-20

Spring循环依赖的解决方法

这篇文章主要介绍Spring循环依赖的解决方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B
2023-06-06

java循环依赖如何解决

在Java中,循环依赖是指两个或多个类之间存在相互依赖关系,导致无法编译或运行的情况。解决循环依赖的方法可以有以下几种:重构代码:检查循环依赖的类之间的关系,尝试将其解耦。可以考虑将某些共同的功能抽取出来,放到一个新的类中,然后让循环依赖的
2023-10-23

spring循环依赖问题如何解决

本篇内容介绍了“spring循环依赖问题如何解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、三种循环依赖的情况①构造器的循环依赖:这种
2023-07-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录