Spring源码分析の循环依赖

news/2025/2/26 17:04:21

文章目录

  • 前言
  • 一、循环依赖问题
  • 二、循环依赖的解决
  • 三、整体流程分析


前言

  常见的可能存在循环依赖的情况如下:

  1. 两个bean中互相持有对方作为自己的属性。
    在这里插入图片描述在这里插入图片描述  类似于:
    在这里插入图片描述
  2. 两个bean中互相持有对方作为自己的属性,且在构造时就需要传入:
    在这里插入图片描述在这里插入图片描述  类似于:
    在这里插入图片描述
  3. 在某个bean中注入自身:
    在这里插入图片描述
      其中第二种构造方法的循环依赖一般情况下是无解的,除非加上@Lazy注解。本篇重点分析第一种循环依赖Spring是如何解决的。

一、循环依赖问题

  Spring在创建一个bean时,简单来说会经过实例化bean,属性注入,初始化的操作。当出现第一种循环依赖时,可能会经历以下的过程:
  AService

  1. 去单例池中找有无AService实例,此时没有,执行doCreateBean
  2. createBeanInstance创建出AService实例。
  3. 执行AService实例的属性填充。
  4. AService的初始化、初始化前。
  5. AService的初始化后。
  6. 放入单例池。

  其中在执行AService实例的属性填充这一步,会根据@AutoWired的注入点,去寻找BService实例:

  1. 去单例池中找有无BService实例,此时没有,执行doCreateBean
  2. createBeanInstance`创建出BService实例。
  3. 执行BService实例的属性填充。
  4. BService的初始化、初始化前。
  5. BService的初始化后。
  6. 放入单例池。

  其中在执行BService实例的属性填充这一步,会根据@AutoWired的注入点,发现需要填充AService实例,这就出现了循环依赖的问题。

二、循环依赖的解决

  那么Spring是如何解决循环依赖的?主要是通过三级缓存的机制去实现的:
在这里插入图片描述singletonObjects :一级缓存 earlySingletonObjects 二级缓存 singletonFactories 三级缓存

  可以看到,首先在doCreateBean的方法中,bean实例化后,属性填充之前,先有一段图上的逻辑:

  • 判断当前的bean是否是单例的,以及是否支持循环依赖(默认是true),以及singletonsCurrentlyInCreation集合中是否包含当前bean。
  • 如果满足条件,就把当前的bean的名称,和一段lambda表达式,放入singletonFactories集合中。
java">	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			//单例池中没有该bean
			if (!this.singletonObjects.containsKey(beanName)) {
				//向单例工厂缓存当前bean名称以及对应的lambda表达式
				this.singletonFactories.put(beanName, singletonFactory);
				//从早期单例对象的缓存中去除当前的bean
				this.earlySingletonObjects.remove(beanName);
				//向已注册的单例bean集合中添加当前bean名称
				this.registeredSingletons.add(beanName);
			}
		}
	}

在这里插入图片描述  这里的lambda表达式,主要是为了返回一个可以被外部提前访问的 bean 实例。注意:lambda表达式不是在此处执行,而是先放入了earlySingletonObjects集合中!

java">	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				//如果 Spring AOP 代理在这里介入,那么 getEarlyBeanReference() 可能会返回一个动态代理对象,而不是原始 bean
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

在这里插入图片描述返回可能经过 AOP 代理的 bean

在这里插入图片描述判断是否需要AOP

  并且在执行doGetBean时,会首先执行getSingleton方法:
在这里插入图片描述
  在getSingleton方法中,运用了双检锁模式,避免在加锁后其它线程已经创建并缓存了该 bean。并且上面提到的lambda表达式,会在此处真正地去执行

java">@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 尝试从单例池中获取当前 bean 的实例,避免加锁操作,提高性能
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果单例池(一级缓存)中没有找到,并且当前 bean 正在创建过程中(循环依赖的处理)
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 尝试从早期单例池(二级缓存)中获取当前 bean(即尚未完全初始化的对象)
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 如果在早期单例池中也没有找到,并且允许获取早期引用(通常是为了解决循环依赖)
        if (singletonObject == null && allowEarlyReference) {
            // 加锁,避免并发创建同一个 bean 导致不一致的问题
            synchronized (this.singletonObjects) {
                // 再次检查,避免在加锁后其它线程已经创建并缓存了该 bean
                singletonObject = this.singletonObjects.get(beanName);
                // 如果单例池中依然没有找到该 bean
                if (singletonObject == null) {
                    // 尝试从早期单例池中获取,如果仍然没有找到
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    // 如果早期单例池中也没有找到,尝试从工厂中获取 bean(即懒加载)
                    if (singletonObject == null) {
                        // 获取 bean 的工厂方法(三级缓存)
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        // 如果工厂方法存在,说明该 bean 尚未初始化,且支持懒加载
                        if (singletonFactory != null) {
                            // 使用工厂创建 bean 并缓存到早期单例池中,防止循环依赖
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject); // 缓存早期对象
                            this.singletonFactories.remove(beanName); // 移除工厂方法,因为 bean 已经创建
                        }
                    }
                }
            }
        }
    }
    // 返回获取到的 singletonObject,如果没有找到,则返回 null
    return singletonObject;
}

  这里的isSingletonCurrentlyInCreation方法,是判断当前的bean名称是否在singletonsCurrentlyInCreation集合中,那么bean是在什么时候存入该集合的呢?答案是在下方重载的getSingleton方法中:
在这里插入图片描述在这里插入图片描述在这里插入图片描述无论if条件是否成立,都会把当前的bean名称放入singletonsCurrentlyInCreation集合中

  而在执行完createBean(包括实例化,依赖注入,初始化)之后,会执行addSingleton方法:
在这里插入图片描述
  会将当前bean从二级、三级缓存中移除,并放入单例池中,表示该bean已经完全创建完成。

java">protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		//将当前初始化完成的bean存入单例池(一级缓存)
		this.singletonObjects.put(beanName, singletonObject);
		//从三级缓存中删除当前bean
		this.singletonFactories.remove(beanName);
		//从二级缓存中删除当前bean
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

三、整体流程分析

在这里插入图片描述A和B的创建流程 蓝色代表A 绿色代表B

  A尝试从单例池中获取
在这里插入图片描述  条件不满足,返回null
在这里插入图片描述  进入getSingleton
在这里插入图片描述
  进入getSingleton,执行beforeSingletonCreation,将A放入singletonsCurrentlyInCreation中,代表A正在被创建。
在这里插入图片描述  进入doCreateBeanaddSingletonFactory方法:
在这里插入图片描述  进入doCreateBeanaddSingletonFactory方法,将A放入三级缓存中
在这里插入图片描述  执行A的属性填充(A有一个属性为B):
在这里插入图片描述  尝试从容器中获取B
在这里插入图片描述在这里插入图片描述  这里的条件依旧不满足,返回null:
在这里插入图片描述  进入getSingleton,执行beforeSingletonCreation,同样将B放入singletonsCurrentlyInCreation中,代表B正在被创建。
在这里插入图片描述  进入doCreateBeanaddSingletonFactory

在这里插入图片描述  同样将B放入三级缓存
在这里插入图片描述


  此时的缓存情况:A和B只在二级缓存中
在这里插入图片描述


  进行B的属性填充**(B中有一个A属性)**
在这里插入图片描述  再次尝试从容器中获取A
在这里插入图片描述在这里插入图片描述  此时的条件满足:
在这里插入图片描述  从三级缓存中取出A的lambda表达式执行:
在这里插入图片描述在这里插入图片描述  此时的AB属性是没有值的
在这里插入图片描述  放入二级缓存,并从三级缓存中删除:
在这里插入图片描述


  此时的缓存情况:
在这里插入图片描述


  直接返回,不走createBean的逻辑了。
在这里插入图片描述
  给BA属性赋值,完成属性填充:
在这里插入图片描述  B继续执行初始化

在这里插入图片描述  执行BgetSingletonaddSingleton方法,将B放入单例池,并且清除二三级缓存:
在这里插入图片描述  回到A的属性填充,填充了B属性
在这里插入图片描述  填充完成后,A的B属性有值了,B的A属性也有值了,继续执行A的初始化,完成后将A放入单例池,并且清除二三级缓存:在这里插入图片描述


  此时的二三级缓存全部清空:
在这里插入图片描述


  至此整个流程全部结束。
  为什么要加入三级缓存?因为上面的过程只是普通情况,还需要考虑到AOP的情况。如果开启了AOP,那么会在初始化后,**基于切面生成一个代理对象。**而循环依赖的触发时机是在属性注入时,如果只使用普通的缓存,会导致解决循环依赖时注入的对象是普通对象,而最终的对象是代理对象,产生不一致的情况。
  applyBeanPostProcessorsAfterInitialization是初始化后执行的方法,也是在AOP的场景下生成代理对象的方法:
在这里插入图片描述  如果开启了AOP,那么在循环中会执行AbstractAutoProxyCreatorpostProcessAfterInitialization方法生成代理,在这一步中会判断当前Bean是否已经在解决了循环依赖的过程中进行了AOP,如果已经进行过了,就不会再次生成代理,保证代理对象的唯一性。
在这里插入图片描述


http://www.niftyadmin.cn/n/5868966.html

相关文章

从两地三中心到多地多中心,OceanBase如何实现金融级高可用

“两地三中心”已成为金融领域基准的容灾部署模式。本文将简要阐述金融行业容灾架构中“两地三中心”的具体要求和部署&#xff0c;并进一步探讨OceanBase在实现“两地三中心”标准后&#xff0c;再至“多地多中心”部署中所展现的独特优势与特点。 商业银行的容灾要求 《商业…

智能语音机器人为电销行业带来一场革命性的变化

我们知道一个企业的发展离不开销售&#xff0c;因为它能直接带来盈利&#xff0c;所以很多企业极其重视电话销售这一模式。但随着人工智能技术的发展&#xff0c;许多企业都采用了智能语音机器人来替代人工&#xff0c;甚至替代了传统呼叫中心运/营模式&#xff0c;在极大提升企…

SQL SERVER日常运维巡检系列之-性能

前言 做好日常巡检是数据库管理和维护的重要步骤&#xff0c;而且需要对每次巡检日期、结果进行登记&#xff0c;同时可能需要出一份巡检报告。 本系列旨在解决一些常见的困扰&#xff1a; 不知道巡检哪些东西不知道怎么样便捷体检机器太多体检麻烦生成报告困难&#xff0c;无…

【Ambari】Ranger KMS

目录 一、Ranger KMS介绍 二、KMS基于Ranger插件安装 一、Ranger KMS介绍 Ranger KMS是把数据存储入后台数据库中。通过Ranger Admin可以集中化管理KMS服务。 Ranger KMS有三个优点 l Key management Ranger admin 提供了创建&#xff0c;更新&#xff0c;删除密钥的Web UI…

仿12306项目(1)

雪花算法 为了高效的生成有序且唯一的ID&#xff0c;可以采用雪花算法来进行实现&#xff0c;为什么不去采用UUID呢&#xff1f;首先&#xff0c;UUID是一个128位的值&#xff0c;相较于雪花算法生成的64位的值&#xff0c;长了很多&#xff0c;在数据库中存储时耗费的时间更长…

物联网平台建设方案一

系统概述 构建物联网全域支撑服务能力&#xff0c;为实现学院涵盖物联网设备的全面感知、全域互联、全程智控、全域数字基底、全过程统筹管理奠定基础&#xff0c;为打造智能化提供坚实后台基石。 物联网平台向下接入各种传感器、终端和网关&#xff0c;向上通过开放的实施分…

大语言模型中的梯度值:深入理解与应用

1. 摘要 ​ 梯度是微积分中的一个基本概念&#xff0c;在机器学习和深度学习中扮演着至关重要的角色。特别是在大语言模型&#xff08;LLM&#xff09;的训练过程中&#xff0c;梯度指导着模型参数的优化方向。 本报告首先由浅入深地介绍梯度的概念&#xff0c;包括其数学定义…

llama.cpp 一键运行本地大模型 - Windows

文章目录 llama.cpp 一键运行本地大模型 - Windows嘿&#xff0c;咱来唠唠 llama.cpp 这玩意儿&#xff01;gguf 格式是啥&#xff1f;咱得好好说道说道基座模型咋选&#xff1f;所需物料&#xff0c;咱得准备齐全咯核心命令&#xff0c;得记牢啦运行方式咋选&#xff1f;测试应…