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

面试题24:Spring循环依赖

Spring循环依赖:原理、问题与解决方案

一、循环依赖的定义

在Spring框架中,循环依赖是指两个或多个Bean之间相互依赖,形成一个闭环的情况。例如,BeanA依赖于BeanB,同时BeanB又依赖于BeanA。这种情况在通过setter方法注入或者@Autowired属性注入时较为常见。

代码示例

// BeanA类
public class BeanA {private BeanB beanB;//  setter方法注入BeanBpublic void setBeanB(BeanB beanB) {this.beanB = beanB;}
}// BeanB类
public class BeanB {private BeanA beanA;//  setter方法注入BeanApublic void setBeanA(BeanA beanA) {this.beanA = beanA;}
}

二、Spring三级缓存机制

(一)缓存结构

  1. ​一级缓存​

    • 作用:存放完全初始化好的Bean实例。

    • 存储类型:ConcurrentHashMap类型的SingletonObjects

    • 意义:当需要获取一个已经完全初始化的Bean时,可以直接从一级缓存中获取,提高获取Bean的效率。

  2. ​二级缓存​

    • 作用:存放未完成属性注入的Bean实例。

    • 存储类型:HashMap类型的earlySingletonObjects

    • 意义:在Bean实例创建过程中,当实例已经创建但还未完成属性注入等操作时,先将这个“半成品”的Bean实例存放在二级缓存中,以应对可能出现的其他Bean对它的依赖。

  3. ​三级缓存​

    • 作用:存放Bean的ObjectFactory

    • 存储类型:HashMap类型的singletonFactories

    • 意义:ObjectFactory是一个用于懒加载Bean的接口,它可以在需要时提供Bean的实例,而不是立即创建和存储它。这一机制有助于解决setter方法和@Autowired属性注入的循环依赖问题。

(二)Bean创建流程与三级缓存的关系

  1. 当创建一个Bean(例如BeanA)时,首先会将BeanA的ObjectFactory放入三级缓存。

  2. 在BeanA实例创建完成后,会进行属性注入。如果在注入属性时发现需要BeanB,那么就会开始创建BeanB。

  3. 同样,创建BeanB时,也会将BeanB的ObjectFactory放入三级缓存。

  4. 在注入BeanB的属性时,如果发现需要BeanA,此时就可以从三级缓存中获取到BeanA的ObjectFactory,并通过它来获取BeanA的实例(这个实例可能是还未完全初始化的),然后将这个实例注入到BeanB中,之后再将这个实例放入二级缓存,继续完成BeanB的其他初始化操作。最后,将完全初始化后的BeanB实例放入一级缓存,再将BeanB注入到BeanA中,从而完成BeanA的初始化。

三、构造器注入的循环依赖问题

(一)问题产生的原因

  1. 三级缓存机制只有在对象实例已经创建(即构造方法执行完后)才能生效,从而将实例暴露到earlySingletonObjects中。

  2. 在使用构造器注入时,Spring必须先创建依赖对象才能调用构造器。例如,BeanA的构造器必须要BeanB的实例作为参数,而BeanB又等着BeanA的实例,这就导致Spring在创建BeanA时,由于需要BeanB的实例,但BeanB的创建又依赖于BeanA的实例,使得Spring根本无法创建任何一个Bean的实例,从而导致了死锁。

(二)解决方案:@Lazy注解

  1. ​原理​

    • 可以通过在构造器中对参数添加@Lazy注解,让Spring注入一个代理对象。这个代理对象并不会立即触发依赖Bean的真正初始化,而是在真正调用该依赖Bean的方法时才会触发其初始化。

  2. ​示例代码修改​

    • 对于BeanA的构造器进行修改:

    public class BeanA {private BeanB beanB;// 使用@Lazy注解@Autowiredpublic BeanA(@Lazy BeanB beanB) {this.beanB = beanB;}
    }

四、其他相关问题与最佳实践

(一)其他解决方案探讨

  1. ​@PostConstruct注解​

    • 虽然不能直接解决构造器注入的循环依赖问题,但可以在Bean的初始化阶段进行一些操作,例如在@PostConstruct注解标注的方法中进行一些属性的设置或者资源的加载,这些操作可以在依赖注入完成之后进行。

  2. ​使用setter注入​

    • 在可能的情况下,推荐使用setter注入而非构造器注入。因为setter注入不会在实例创建时就需要依赖的Bean,而是在Bean实例创建完成后再进行属性注入,这样可以避免构造器注入带来的循环依赖问题。

(二)Bean作用域对循环依赖的影响

  1. 对于prototype作用域的Bean,由于其每次获取时都会创建新的实例,所以在处理循环依赖时相对复杂。如果出现循环依赖,可能会导致更多的问题,因为无法像单例Bean那样可以通过缓存机制来解决。

(三)最佳实践总结

  1. 在设计Bean之间的依赖关系时,尽量避免循环依赖的出现。如果无法避免,优先考虑使用setter注入或者字段注入(通过@Autowired)的方式。

  2. 如果必须使用构造器注入且出现了循环依赖问题,可以考虑使用@Lazy注解来解决。

  3. 在使用@PostConstruct注解时,要确保其标注的方法中的操作不会引入新的循环依赖或者影响Bean的正常初始化顺序。

http://www.dtcms.com/a/342425.html

相关文章:

  • 为什么可以kvcache
  • 8月21日作业
  • 【python实用小脚本-194】Python一键给PDF加水印:输入文字秒出防伪文件——再也不用开Photoshop
  • 文字识别——PDF OCR
  • electron-vite_19配置环境变量
  • 亚马逊站外推广效能重构:自然排名提升的逻辑与实操边界
  • 底层逻辑颠覆者:Agentic BI如何通过“Data + AI Agent”架构重构数据价值链?
  • 【C++】继承(详解)
  • 开心实习之第二十九天
  • Bartender 5 多功能菜单栏管理(Mac电脑)
  • NPM组件 @angular_devkit/core 等窃取主机敏感信息
  • 消息中间件选型分析:RabbitMQ vs RocketMQ vs Kafka
  • java生成带水印的pdf文件
  • 【从零构建企业级线程池管理系统:Python并发编程实战指南】
  • 医疗智能体高质量问诊路径开发:基于数智立体化三维评估框架(go语言)
  • [新启航]长轴深孔检测 - 激光频率梳 3D 轮廓检测
  • Go语言中的迭代器模式与安全访问实践
  • Linux应用层开发--线程池介绍
  • 【网络运维】Shell:变量数值计算
  • redis-缓存-双写一致性
  • 【Django:基础知识】
  • 掌控不平等的力量:深入解析帕雷托分布与二八法则的数学内核
  • python测试开发django-1.开始hello world!
  • 《零基础入门AI:深度学习之NLP基础学习》
  • 在Python中, list相减 要从一个列表(valid_points)中排除另一个列表(yuanjian_jiaodian)的所有元素
  • Linux CentOS 安装 .net core 3.1
  • 银河麒麟V10系统离线安装zabbix-agent教程
  • 18维度解密·架构魔方:一览无遗的平衡艺术
  • nginx-重定向-正则表达式-路由匹配优先级
  • Qt截图工具项目开发教程 - 从零开始构建系统截图工具