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

Spring 循环依赖

优质博文:IT-BLOG-CN

一、问题现状

Spring框架中,循环依赖Circular Dependency是指两个或多个Bean相互依赖,形成一个循环引用。例如,Bean A依赖于Bean B,而Bean B又依赖于Bean A。这种情况可能会导致Spring容器在创建Bean时出现问题。如下:

public class A { private final B b; public A(B b) { this.b = b; } } public class B { private final A a; public B(A a) { this.a = a; } }

启动时会出现如下错误:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'A': Requested bean is currently in creation: Is there an unresolvable circular reference?

二、解决循环依赖的方法

【1】构造器注入:构造器注入不支持循环依赖,因为在Spring容器尝试创建Bean时,它需要立即解析所有的构造函数参数,这会导致循环依赖问题。因此,避免使用构造器注入来解决循环依赖。@Lazy注解可以延迟Bean的初始化,使得Spring在构造器注入时也能解决循环依赖问题。具体来说,@Lazy会告诉Spring在第一次使用Bean时才进行初始化,而不是在容器启动时立即初始化。

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component public class A { private final B b; @Autowired public A(@Lazy B b) { this.b = b; } } @Component public class B { private final A a; @Autowired public B(@Lazy A a) { this.a = a; } }

【2】Setter注入:Setter注入可以解决循环依赖问题,因为Spring容器可以首先创建Bean的实例,然后再注入依赖。

public class A { private B b; public void setB(B b) { this.b = b; } } public class B { private A a; public void setA(A a) { this.a = a; } }

【3】@Autowired注解:使用@Autowired注解进行Setter注入或者字段注入,也可以解决循环依赖问题。

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

【4】@Lazy注解:使用@Lazy注解可以延迟Bean的初始化,从而解决循环依赖问题。

public class A { @Autowired @Lazy private B b; } public class B { @Autowired @Lazy private A a; }

【5】使用ObjectFactoryProvider使用ObjectFactoryProvider可以在需要时才获取Bean实例,从而解决循环依赖问题。

public class A { @Autowired private ObjectFactory<B> bFactory; public void someMethod() { B b = bFactory.getObject(); // 使用B } } public class B { @Autowired private ObjectFactory<A> aFactory; public void someMethod() { A a = aFactory.getObject(); // 使用A } }

【6】配置allow-circular-references: true用于控制是否允许Bean之间的循环依赖。true:允许Bean之间存在循环依赖。Spring容器会尝试通过创建Bean的代理对象来解决循环依赖问题。这是默认行为。但从设计和架构的角度来看,尽量避免循环依赖是更好的做法。

spring: main: allow-circular-references: true

三、三级缓存的组成

首先,我们要知道Spring在创建Bean的时候默认是按照自然排序进行创建的,所以第一步Spring会去创建A

Spring创建Bean的过程中分为三步:
1、实例化:对应方法AbstractAutowireCapableBeanFactory中的createBeanInstance方法,简单理解就是new了一个对象。
2、属性注入:对应方法AbstractAutowireCapableBeanFactorypopulateBean方法,为实例化中new出来的对象填充属性。
3、初始化:对应方法AbstractAutowireCapableBeanFactoryinitializeBean,执行aware接口中的方法,初始化方法,完成AOP代理。

Spring是如何解决循环依赖问题的:三级缓存

三级缓存的组成
一级缓存singletonObjects存储已经完全初始化的单例Bean。类型ConcurrentHashMap<String, Object>
二级缓存earlySingletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入earlySingletonObjects,通常是为了避免循环依赖。类型ConcurrentHashMap<String, Object>
三级缓存singletonFactories存储创建Bean的工厂ObjectFactory,用于解决循环依赖。类型:ConcurrentHashMap<String, ObjectFactory<?>>

/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

四、三级缓存的工作原理

创建Bean实例:Spring容器创建一个Bean时,首先会尝试从一级缓存singletonObjects中获取该Bean。如果获取不到,再尝试从二级缓存earlySingletonObjects中获取。如果仍然获取不到,再尝试从三级缓存singletonFactories中获取。

protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }

提前曝光Bean在创建Bean的过程中,如果检测到循环依赖,Spring会提前将该Bean的一个早期引用(通常是通过ObjectFactory创建的代理对象)放入三级缓存singletonFactories中。

解决循环依赖:当另一个Bean需要依赖这个尚未完全初始化的Bean时,会从三级缓存singletonFactories中获取该Bean的早期引用,并将其放入二级缓存earlySingletonObjects中。

完成初始化:一旦Bean完全初始化完成,Spring会将其从二级缓存earlySingletonObjects中移除,并放入一级缓存singletonObjects中。

案例:如上有两个类AB,它们通过构造器注入互相依赖:
【1】创建A的实例:A依赖B,但B尚未创建。Spring会将A的早期引用(通常是一个代理对象)放入三级缓存singletonFactories中。
【2】创建B的实例:B依赖ASpring会从三级缓存singletonFactories中获取A早期引用,并将其放入二级缓存earlySingletonObjects中。通过早期引用可知,B注入的是A的引用,所以最终拿到的是一个完整的A对象。
【3】完成B的初始化:B完全初始化后,放入一级缓存singletonObjects中。
【4】完成A的初始化:A获取到B的完全初始化的实例后,完成自身初始化,并放入一级缓存singletonObjects中。

五、spring 循环依赖为什么使用三级缓存而不是二级缓存

【1】代理对象的创建:在某些情况下,Spring需要为bean创建代理对象(例如,使用AOP时)。代理对象的创建通常在bean初始化的后期阶段进行。

如果只使用二级缓存,意味着所有Bean在实例化后就要完成AOP代理,在某些情况下,Spring可能无法正确地创建代理对象,因为代理对象的创建依赖于完整的bean初始化过程,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

三级缓存中的对象工厂可以确保在需要时创建代理对象,并将其放入二级缓存,从而确保代理对象可以在循环依赖中正确地被引用。

【2】延迟创建早期引用:三级缓存中的对象工厂允许Spring在需要时延迟创建早期引用,而不是立即创建。这种延迟创建机制可以确保在某些特殊情况下,bean可以在完全初始化之前被引用。

通过这种方式,Spring可以更灵活地处理各种复杂的依赖关系和代理对象的创建。

http://www.jsqmd.com/news/450410/

相关文章:

  • Spring 核心技术解析【纯干货版】- XII:Spring 数据访问模块 Spring-R2dbc 模块精讲
  • 2026碳酸镁市场佼佼者盘点:优秀生产厂家一览,知名的碳酸镁供应商技术实力与市场口碑领航者 - 品牌推荐师
  • Android项目创建指南-Java版
  • 告别技术门槛|手把手教你,在中文平台轻松玩转MCP,联动大模型高效干活!
  • 2026年海南抖音短视频代运营公司5强推荐榜单公布 - 精选优质企业推荐榜
  • 企业级学院个人信息管理系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • 2026年氧化铝空心球厂家盘点:这几家市场表现值得关注,氧化铝空心球/偏高岭土/白刚玉,氧化铝空心球直销厂家推荐排行榜 - 品牌推荐师
  • 2026年深圳抖音短视频代运营公司排行榜发布 - 精选优质企业推荐榜
  • 京东E卡回收,如何挑选靠谱平台 - 京顺回收
  • 2026年吉林抖音短视频代运营服务商5强推荐榜单发布 - 精选优质企业推荐榜
  • 2026年山西抖音短视频代运营公司排行榜公布 - 精选优质企业推荐榜
  • 2026年广东抖音短视频代运营服务商5强推荐榜单发布 - 精选优质企业推荐榜
  • Springboot中CommandLineRunner的用法以及执行顺序的控制
  • 2026年郑州抖音短视频代运营服务商5强推荐榜单公布 - 精选优质企业推荐榜
  • RCE基础----DVWA,Pikachu,CTFHUB
  • MySQL事务隔离与MVCC底层实战
  • SpringBoot中Get请求和POST请求接收参数详解
  • SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
  • springboot+全局异常处理
  • Android跨平台开发与硬件适配技术全景解析
  • Flutter 三方库 personnummer 的鸿蒙化适配指南 - 掌控身份资产、精密编号治理实战、鸿蒙级校验专家
  • python flask的老年人个人健康管理vue统计图
  • SpringBoot后端服务重定向
  • Springboot中mybatis的使用
  • Springboot中SLF4J详解
  • 5G NR PUSCH开环功控
  • GPT系列技术演进:从单向建模到多模态世界的架构革新
  • springboot和springframework版本依赖关系
  • springboot与springcloud以及springcloudalibaba版本对照
  • springboot+mybaties项目中扫描不到@mapper注解的解决方法