spring的三级缓存与源码分析--解决循环依赖

news2025/1/12 4:50:50

三级缓存介绍

Spring 通过三层缓存来处理循环依赖,这些缓存分别是:

一级缓存(内存中的 singletonObjects)
二级缓存(earlySingletonObjects)
三级缓存(singletonFactories)

1. 一级缓存(singletonObjects)
这是 Spring 容器中的主要缓存区,存储已经完全初始化的单例 bean。当一个 bean 完全初始化并且所有依赖项已经注入完毕时,它会被放入这个缓存中。存储完全初始化好的单例 Bean。

2. 二级缓存(earlySingletonObjects)
这是一个存放“提前曝光”的 bean 的缓存区域。它存储的是那些已经开始实例化但还没有完成初始化的 bean。这个缓存用于解决那些在构造器注入过程中发生的循环依赖问题。存储原始的、未完全初始化的单例 Bean(提前暴露)。

3. 三级缓存(singletonFactories)
这是一个用于存储 ObjectFactory 的缓存区域,ObjectFactory 是一个工厂接口,用于创建 bean 实例。这个缓存用于在 bean 实例化过程中还未完成时提供一个工厂方法。存储原始的、带有 ObjectFactory 的单例 Bean。

循环依赖的解决流程

  • 创建 Bean 实例:当 Spring 容器创建一个 bean 实例时,它会首先检查一级缓存。如果在一级缓存中找不到该 bean,容器会继续执行实例化过程。
  • 二级缓存:在 bean 实例化的过程中,如果发现依赖的 bean 尚未完成初始化(即在创建过程中的“提前曝光”阶段),Spring 会将这个 bean 放入二级缓存中。
  • 三级缓存:如果需要依赖的 bean 还在创建过程中,Spring 会将一个 ObjectFactory 放入三级缓存中。这个 ObjectFactory 可以用来在稍后的阶段(即 bean 完全创建后)获取 bean 的实例。
  • 注入依赖:当 Spring 创建一个 bean 时,它会尝试从二级缓存中获取其依赖项。如果成功,它将使用这些“提前曝光”的 bean 完成注入。如果依赖项不在二级缓存中,Spring 会从三级缓存中使用 ObjectFactory 来获取最终的 bean 实例。
  • 完成初始化:一旦所有依赖项都被注入,bean 会被放入一级缓存中,并完成初始化过程。此时,它会被完全初始化,并准备好提供服务。

例如:

public class A {
    @Autowired
    private B b;
}

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

在这种情况下,Spring 会使用三层缓存机制来处理这两个 bean 的循环依赖。具体流程如下:

1. 初始化 A 的过程
  1. 创建实例:Spring 发现需要创建 A 的实例。它首先会在一级缓存中查找 A,如果没有找到,再到二级缓存查找,最后到三级缓存查找。由于这是第一次创建 A,所以在所有缓存中都找不到。
  2. 实例化:Spring 创建 A 的原始实例(但不进行依赖注入)。此时,A 的原始实例会放到三级缓存中。
  3. 依赖注入:Spring 发现 A 依赖 B,于是开始创建 B
2. 初始化 B 的过程
  1. 创建实例:Spring 发现需要创建 B 的实例。它首先会在一级缓存中查找 B,如果没有找到,再到二级缓存查找,最后到三级缓存查找。由于这是第一次创建 B,所以在所有缓存中都找不到。
  2. 实例化:Spring 创建 B 的原始实例(但不进行依赖注入)。此时,B 的原始实例会放到三级缓存中。
  3. 依赖注入:Spring 发现 B 依赖 A。它会在一级缓存中查找 A,没有找到;再到二级缓存中查找,还是没有找到;最后到三级缓存中查找,找到了 A 的原始实例。
  4. 解决循环依赖:从三级缓存中获取到 A 的原始实例后,Spring 将 A 注入到 B 中。
  5. 完成 B 的初始化B 完全初始化后,Spring 将 B 的实例从三级缓存移到一级缓存。
3. 完成 A 的初始化
  1. 解决循环依赖:回到 A 的初始化过程,Spring 将完全初始化好的 B 注入到 A 中。
  2. 完成 A 的初始化A 完全初始化后,Spring 将 A 的实例从三级缓存移到一级缓存。

创建bean源码分析

AbstractAutowireCapableBeanFactory类的doCreateBean方法

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        // 实例化 Bean
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            // 缓存了先前创建的 Bean 实例(如果有的话)。如果 Bean 是单例的,尝试从缓存中取出。
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            // createBeanInstance 实际创建 Bean 实例的方法。通过反射调用构造函数或工厂方法来实例化 Bean。
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // instanceWrapper:包装了 Bean 实例和类型信息。getWrappedInstance() 获取实际的 Bean 实例,getWrappedClass() 获取 Bean 的类型
        Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {
            mbd.resolvedTargetType = beanType;
        }
        
        // Allow post-processors to modify the merged bean definition.
        // 应用合并的 Bean 定义后处理器
        // mbd.postProcessingLock 确保在多线程环境中对 Bean 定义后处理的线程安全性。
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    // 应用 Bean 定义后处理器。用于修改合并的 Bean 定义,比如处理 @PostConstruct 注解的方法
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Post-processing of merged bean definition failed", ex);
                }
                mbd.postProcessed = true;
            }
        }

        // 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));  // 如果 Bean 是单例的,并且允许循环依赖,且当前正在创建这个单例 Bean,则允许提前缓存
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            // addSingletonFactory:将一个 ObjectFactory 放入三级缓存,以便在 Bean 完全初始化后获取实例。这个 ObjectFactory 会在稍后阶段调用 getEarlyBeanReference 来获取 Bean 实例。
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // Initialize the bean instance.
        // 填充 Bean 属性和初始化 Bean
        Object exposedObject = bean;
        try {
            // populateBean:填充 Bean 的属性
            populateBean(beanName, mbd, instanceWrapper);
            // initializeBean:初始化 Bean,比如执行 @PostConstruct 注解的方法,或者其他初始化逻辑
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            // 处理可能发生的异常,并根据不同的情况抛出适当的异常
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            }
            else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }
        
        // 检查并处理循环依赖
        if (earlySingletonExposure) {
            // getSingleton(beanName, false):尝试从一级缓存中获取 Bean 实例。如果 Bean 尚未完全初始化,它会从三级缓存中获取到
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
                // hasDependentBean(beanName):检查是否有其他 Bean 依赖于当前 Bean
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        // removeSingletonIfCreatedForTypeCheckOnly(dependentBean):移除仅用于类型检查的 Bean。
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {
                        // BeanCurrentlyInCreationException:如果发现有依赖于当前 Bean 的其他 Bean,这个异常会被抛出,提示 Bean 已被提前注入而且存在循环依赖
                        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.");
                    }
                }
            }
        }

        // Register bean as disposable.
        // 注册 Bean 为可销毁的
        try {
            // registerDisposableBeanIfNecessary:注册 Bean 的销毁回调方法(如果有的话),以便在容器销毁时调用
            registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }
        // 返回 Bean 实例
        return exposedObject;
    }

关键点

		// 提前缓存单例以解决循环依赖
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));  // 如果 Bean 是单例的,并且允许循环依赖,且当前正在创建这个单例 Bean,则允许提前缓存
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// addSingletonFactory:将一个 ObjectFactory 放入三级缓存,以便在 Bean 完全初始化后获取实例。这个 ObjectFactory 会在稍后阶段调用 getEarlyBeanReference 来获取 Bean 实例。
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

一级缓存(内存中的 singletonObjects)
二级缓存(earlySingletonObjects)
三级缓存(singletonFactories)

		// 检查并处理循环依赖
		if (earlySingletonExposure) {
			// getSingleton(beanName, false):尝试从一二级缓存中获取 Bean 实例。如果 Bean 尚未完全初始化,它会从三级缓存中获取到
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				// hasDependentBean(beanName):检查是否有其他 Bean 依赖于当前 Bean
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						// removeSingletonIfCreatedForTypeCheckOnly(dependentBean):移除仅用于类型检查的 Bean。
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						// BeanCurrentlyInCreationException:如果发现有依赖于当前 Bean 的其他 Bean,这个异常会被抛出,提示 Bean 已被提前注入而且存在循环依赖
						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.");
					}
				}
			}
		}

小结

  1. 实例化 Bean:创建 Bean 实例,使用缓存来优化性能和处理循环依赖。
  2. 处理循环依赖:使用三级缓存(singletonFactories、earlySingletonObjects 和 singletonObjects)来处理循环依赖。
  3. 初始化和属性填充:填充 Bean 属性并初始化。
  4. 循环依赖检查:确保在 Bean 初始化过程中处理循环依赖。
  5. 销毁回调:注册销毁方法,以便在 Bean 销毁时调用。

扩展--为什么有三层缓存还是会出现循环依赖问题

可能出现的原因:

  • 构造函数循环依赖

    • 三级缓存只能解决通过属性注入(setter injection)或字段注入(field injection)方式的循环依赖。
    • 如果两个或多个 Bean 之间存在构造函数循环依赖(即 A 的构造函数依赖于 B,B 的构造函数依赖于 A),那么即使有三级缓存机制,Spring 也无法解决这种循环依赖。
@Component
public class A {
    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }
}

  • Bean 的作用域(Scope)

    • 三级缓存机制主要解决的是单例作用域(singleton scope)的循环依赖。
    • 对于多例作用域(prototype scope)的 Bean,Spring 不会缓存这些 Bean,因此无法解决原型作用域 Bean 的循环依赖。

spring bean的作用域:

Singleton(单例),Prototype(多例),Request(请求),Session(会话),Application(应用)

  • 自定义 Bean 后处理器(BeanPostProcessor)

    • 如果自定义的 BeanPostProcessor 中存在某些逻辑,导致提前访问 Bean 的实例,可能会打破三级缓存的逻辑,导致循环依赖异常。
    • 特别是 @Autowired 注解处理器或其他涉及提前引用 Bean 的处理器,可能导致 Bean 被提前实例化,从而引发循环依赖异常。
  • FactoryBean 的复杂依赖

    • FactoryBean 的复杂依赖关系可能导致 Spring 在创建 Bean 实例时遇到困难,从而引发循环依赖异常。
  • AOP 动态代理

    • AOP 动态代理的创建过程可能会打破三级缓存的逻辑。如果在代理创建过程中需要依赖某个 Bean,而这个 Bean 正在创建中,也可能导致循环依赖异常。

解决方法:

1. 使用 @Lazy 注解--懒加载

使用 @Lazy 注解可以推迟 Bean 的初始化,直到真正需要该 Bean 时才进行初始化。这样可以打破循环依赖链。

2. 使用 @PostConstruct 注解

使用 @PostConstruct 注解在 Bean 初始化之后执行某些操作,这样可以将依赖的初始化推迟到 Bean 完全创建之后。

3. 使用合适的注入方式

如:构造函数注入+ ObjectFactory; @Autowired 注解放在字段上; 使用 Setter 方法注入依赖

测试循环依赖

测试1:使用@Autowired srping自动处理

成功启动

测试2:使用构造函数注入--循环依赖出现

启动出现循环依赖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1968861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解C语言结构体

目录 引言 一. 结构体的基本概念 1.结构体的声明 2. 结构体变量的创建和初始化 3. 结构体成员访问操作符 4.结构体的特殊声明 1. 匿名结构体 2. 嵌套结构体 3.结构体自引用 4. typedef 声明 二、结构体内存对⻬ 1.对⻬规则 2.为什么存在内存对⻬? 3.修改默认对齐…

ffmpeg命令-Windows下常用最全

查询命令 参数 说明 -version 显示版本。 -formats 显示可用的格式&#xff08;包括设备&#xff09;。 -demuxers 显示可用的demuxers。 -muxers 显示可用的muxers。 -devices 显示可用的设备。 -codecs 显示libavcodec已知的所有编解码器。 -decoders 显示可用…

基于SpringBoot+Vue的小区物业管理系统(带1w+文档)

基于SpringBootVue的小区物业管理系统(带1w文档) 基于SpringBootVue的小区物业管理系统(带1w文档) 小区物业管理系统采用B/S(Browser/Server)架构和MVC模型进行设计开发。在B/S架构下&#xff0c;用户在浏览器端进行使用&#xff0c;主要工作通过服务器端进行实现&#xff0c;用…

电脑缺少dll文件怎么解决?10款dll修复工具大盘点,赶紧收藏起来!

电脑缺少dll文件怎么解决&#xff1f;DLL&#xff08;动态链接库&#xff09;是一种重要文件&#xff0c;包含了一系列指令&#xff0c;用于运行几乎所有 Win10、Win8和 Win7的程序。如果Windows 操作系统中缺少DLL文件&#xff0c;您可能会无法启动所需的程序或应用。在 Win10…

【AndroidStudio】修改app名称、版本号、图标

文章目录 1. 修改app名称(AndroidManifest.xml-app_name字段)2. 修改app版本号和版本名称3. 修改app图标4. 修改app启动过渡图片 1. 修改app名称(AndroidManifest.xml-app_name字段) 2. 修改app版本号和版本名称 通常是app目录下的build.gradle文件找到“versionCode”和“ver…

基于域名+基于ip+基于端口的虚拟主机+上线商务系统

一、回顾 1.jdk环境 tomcat服务器需要jdk环境 版本对应 ​ tomcat9>jdk1.8 配置系统变量JAVA_HOME sed -i $aexport JAVA_HOME/usr/local/jdk22/ /etc/profile sed -i $aexport PATH$JAVA_HOME/bin:$PATH /etc/profile ​ source /etc/profile ​ java -version java…

LeetCode | 441 | 排列硬币 | 二分查找

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 今天分享的是LeetCode中一道标签为简单的算法题&#xff0c;本质是一道数学题 文章目录 1.题目描述2.题解2.1 公式解法2.2 暴力解法2.3 二分查找 LeetCode链接&#…

【 问题 】 AT32 F413CB 设置SRAM大小为64KB 导致Flash后64KB代码执行变慢 解决办法

背景 AT32的SRAM可以设置为16KB/32KB/64KB的不同大小&#xff0c;设置SRAM大小将导致Flash的部分空间的读写速度减缓&#xff0c;如下图&#xff1a; 这个问题看似不是很大&#xff0c;但是当运行一些很保证实时性&#xff0c;速度性的代码时&#xff0c;就会产生一些问题。 …

react-日期选择器封装

文件 import { useMemo, useState, useEffect } from "react" import dayjs, { Dayjs } from "dayjs" import "dayjs/locale/zh-cn" import "./App.css" dayjs.locale("zh-cn")function SimpleCalendar() {// 当前时间对象…

【数值计算方法】数值积分微分-python实现-p2

原文链接&#xff1a;https://www.cnblogs.com/aksoam/p/18279394 更多精彩&#xff0c;关注博客园主页&#xff0c;不断学习&#xff01;不断进步&#xff01; 我的主页 csdn很少看私信&#xff0c;有事请b站私信 博客园主页-发文字笔记-常用 有限元鹰的主页 内容&#xf…

基于ThinkPHP开发的校园跑腿社区小程序系统源码,包含前后端代码

基于ThinkPHP开发的校园跑腿社区小程序系统源码&#xff0c;包含前后端代码 最新独立版校园跑腿校园社区小程序系统源码 | 附教程 测试环境&#xff1a;NginxPHP7.2MySQL5.6 多校版本&#xff0c;多模块&#xff0c;适合跑腿&#xff0c;外卖&#xff0c;表白&#xff0c;二…

【Android】Kotlin 实现底部弹框日历组件

需求 如下图所示, 底部弹出日历组件 原生插件使用的有一个好处是可以根据你的系统语言切换插件内的语言, 刚好我们这个app面向的国外用户, 而产品对日历组件的日期显示有特别要求, 因此这无疑减少了我们切换语言的开发工作量 代码 1. custom_bottom_datepicker.xml <R…

Java小白入门到实战应用教程-权限修饰符

Java小白入门到实战应用教程-权限修饰符 前言 在前面的内容中我们其实已经接触到了权限修饰符&#xff1a;public 在java中权限修饰符除了public外&#xff0c;还有private、protected、默认权限。 权限修饰符可用来修饰类、成员变量、方法(函数)。 其中修饰类只能用publi…

springboot的表现层/控制层controller开发

第一步&#xff1a;新建文件和注入业务层对象 需要使用的注解&#xff1a; 第一个声明是restful风格开发 第二个是需要设置网页访问路径 RestController RequestMapping("/fuels")//http://localhost/fuels注入服务层对象&#xff1a; Autowiredprivate FuelServ…

晶振输出频率偏差过大的影响

晶体振荡器作为电子系统中的关键部件之一&#xff0c;其输出频率的准确性直接影响着整个系统的性能。无论是实时响应系统还是通信设备&#xff0c;都需要高度精确的频率源来维持其正常运作。本文将探讨晶振输出频率偏差过大会带来的影响&#xff0c;并简要介绍频率稳定性的概念…

【docker】下载docker 容器

1.下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/rhel/9/x86_64/stable/ 2.配置仓库拉取容器镜像 docker镜像 通过软件仓库下载但不安装 挂载并放入一个东西里面&#xff08;存放rpm软件包&#xff0c;并不是软件仓库&#xff09; 采集rpm数据的…

VSCode安装和配置

一、VScode下载和安装 下载 Visual Studio Code - Mac、Linux、Windows 下载完成之后傻瓜式安装即可 二、 左侧活动栏介绍 三、安装中文插件 先下载&#xff0c;在重启即可 四、配置C语言运行环境 1、Code Runner 记得勾选图中的两个选项 2、安装C/C插件 3、安装编译器MinGW…

cpp学习记录05:类和对象02

继承 面向对象的语法思想都差不多继承的好处就是方便 语法&#xff1a; class 子类 : 继承方式 父类 继承方式 公共继承&#xff1a;继承的内容权限不变 保护继承&#xff1a;继承的内容权限变为protected 私有继承&#xff1a;继承的内容权限变为private 这三类继承都不…

1000W长连接,如何建立和维护?千万用户IM 架构设计

1000W长连接&#xff0c;如何建立和维护&#xff1f;千万用户IM 架构设计 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的架构类/设计类…

哈夫曼树及哈夫曼编码

目录 一. 前言 二. 哈夫曼树的构造 三. 哈夫曼编码 一. 前言 在学习哈夫曼树之前&#xff0c;我们先了解几个基本概念。 1.路径&#xff1a;从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。 2.结点的路径长度&#xff1a;两结点间路径上的分支数。 3.树的…