当前位置: 首页 > news >正文

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

三级缓存介绍

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 {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate 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.// 实例化 BeanBeanWrapper 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 属性和初始化 BeanObject 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 依赖于当前 Beanelse 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 依赖于当前 Beanelse 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;@Autowiredpublic A(B b) {this.b = b;}
}@Component
public class B {private A a;@Autowiredpublic 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:使用构造函数注入--循环依赖出现

启动出现循环依赖

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 内衣洗衣机怎么选?五款超耐用内衣洗衣机推荐!
  • 金牌挑战——奥运知识大比拼
  • 【C语言】程序环境,预处理,编译,汇编,链接详细介绍,其中预处理阶段重点讲解
  • 全球汽车用MEMS加速度计市场规划预测:未来六年CAGR为2.8%
  • STM32ADC
  • Java中的抽象类和接口区别
  • TypeScript函数类型:提升函数的类型安全性和可读性
  • 2024年厦门市大数据创新应用大赛重磅开赛,邀您来战!
  • 【数据结构初阶】详解:实现循环队列、用栈实现队列、用队列实现栈
  • 通过内网穿透远程访问自己的项目
  • 【力扣】3128. 直角三角形 JAVA
  • matlab y=sin(x) - 2/π*(x)函数绘制
  • 1.1、centos stream 9安装Kubernetes v1.30集群 环境说明
  • CSS mask-image 实现边缘淡出过渡效果
  • Flink-CDC解析(第47天)
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【React系列】如何构建React应用程序
  • 4个实用的微服务测试策略
  • Docker容器管理
  • Elasticsearch 参考指南(升级前重新索引)
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • MySQL QA
  • Mysql优化
  • React的组件模式
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • 爱情 北京女病人
  • 对象管理器(defineProperty)学习笔记
  • 服务器之间,相同帐号,实现免密钥登录
  • 使用Gradle第一次构建Java程序
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 移动端解决方案学习记录
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ​ArcGIS Pro 如何批量删除字段
  • # Maven错误Error executing Maven
  • # 服务治理中间件详解:Spring Cloud与Dubbo
  • $ git push -u origin master 推送到远程库出错
  • (13):Silverlight 2 数据与通信之WebRequest
  • (8)STL算法之替换
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (二)斐波那契Fabonacci函数
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (理论篇)httpmoudle和httphandler一览
  • (六)激光线扫描-三维重建
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (转)jQuery 基础
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .Family_物联网
  • .NET : 在VS2008中计算代码度量值
  • .net core 使用js,.net core 使用javascript,在.net core项目中怎么使用javascript
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET 服务 ServiceController
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.