Spring是如何解决循环依赖的

什么是循环依赖:

Spring循环依赖是指在Spring管理的Bean(即由Spring容器负责创建、配置和管理的对象)之间,存在一种互相依赖的关系,使得它们形成了一个闭环。具体来说,循环依赖是指两个或多个Bean之间通过构造器注入、setter注入或其他依赖注入方式,以如下形式互相引用:

  1. 直接循环依赖:如Bean A依赖Bean B,同时Bean B反过来也依赖Bean A。
  2. 间接循环依赖:更为复杂的情况,如Bean A依赖Bean B,Bean B依赖Bean C,而Bean C又依赖回Bean A,形成一个闭环。

什么场景会遇到循环依赖:

循环依赖通常发生在以下几种软件设计或实现的场景中,特别是在使用Spring框架进行依赖注入(Dependency Injection, DI)的过程中:

  1. 业务逻辑耦合

    • 当不同业务组件(如服务、模块或类)的设计过于紧密,导致它们在功能或数据处理上互相依赖,可能形成循环依赖。例如,服务A处理某个业务流程的一部分,依赖于服务B提供的某些数据或功能;而服务B在处理另一个业务环节时,又反过来依赖于服务A的输出或操作。
  2. 过度复杂的类结构

    • 类设计中,如果类A包含类B的实例,类B又包含类A的实例,形成直接的双向关联。这种设计往往源于过度细化的职责划分或对关联关系处理不当,导致类之间的边界模糊,互相渗透。
  3. 跨层依赖

    • 在分层架构(如MVC、三层架构等)中,各层之间应当遵循一定的依赖方向(如表现层依赖业务层,业务层依赖数据访问层)。但如果设计不当,可能会出现如控制器依赖服务,服务反过来依赖特定控制器的情况,形成循环。
  4. 事件监听/回调机制

    • 在实现事件驱动或回调功能时,发布者(publisher)和订阅者(subscriber)之间可能存在隐含的循环依赖。例如,发布者注册了订阅者的回调方法,而订阅者在初始化时又依赖于发布者的状态或接口。
  5. 模块间的循环依赖

    • 在大型项目中,不同模块或组件可能通过接口、服务或共享库互相依赖。如果没有合理规划模块边界和依赖关系,可能会导致模块间的循环依赖。
  6. 依赖注入配置不当

    • 使用Spring等DI框架时,通过@Autowired@Inject等注解进行依赖注入。如果在配置Bean间关系时,不慎设置了循环依赖的注入路径,如通过setter注入或字段注入导致Bean A依赖Bean B,Bean B又依赖回Bean A。
  7. 事务管理器与事务代理

    • 在Spring事务管理中,事务代理Bean(如TransactionProxyFactoryBean)可能依赖于事务管理器Bean,而事务管理器Bean在某些配置下可能又依赖于事务代理Bean,形成特殊的循环依赖。

Spring如何解决循环依赖:

Spring解决循环依赖主要针对的是基于setter方法(或字段注入)的循环依赖情况。下面详细说明Spring如何通过三级缓存机制解决setter循环依赖:

Spring的三级缓存机制

  1. 一级缓存(singletonObjects):存储已经完全初始化好的单例Bean,包括所有依赖注入完成、初始化方法调用完毕的Bean。

  2. 二级缓存(earlySingletonObjects):存储已经实例化但尚未完成属性填充(依赖注入)的Bean。这些Bean可以作为其他Bean依赖注入的早期引用。

  3. 三级缓存(singletonFactories):存储能生产半成品Bean(即仅实例化但未完成依赖注入)的ObjectFactory对象。当Bean正在创建且其依赖需要提前暴露时,Spring会将一个能生成该Bean的工厂对象放入此缓存。

解决循环依赖的步骤

假设存在Bean A和Bean B之间的循环依赖:

  1. Bean A开始初始化,Spring为其创建实例,并将其加入三级缓存中,标记为“正在创建”。

  2. Bean A在填充属性时发现依赖Bean B,此时Bean B尚未创建,于是Spring尝试创建Bean B。

  3. Bean B开始初始化,同样加入三级缓存,标记为“正在创建”。

  4. Bean B在填充属性时发现依赖Bean A,此时虽然Bean A尚未完全初始化,但由于其已在三级缓存中(作为一个能生成半成品的工厂),Spring可以从三级缓存中取出ObjectFactory立即创建一个未完成属性注入的Bean A实例,并将其放入二级缓存。

  5. Bean B完成了对半成品Bean A的引用注入,然后继续完成自身的初始化,并将完全初始化好的Bean B放入一级缓存。

  6. Bean A恢复其初始化过程,由于所需依赖Bean B现在已经在一级缓存中可用,Spring可以从一级缓存获取Bean B完成对Bean A的属性注入。

  7. Bean A完成初始化后移入一级缓存,整个循环依赖得以解决。

通过上述过程,Spring利用三级缓存实现了Bean实例的提前曝光,打破了循环依赖链。尽管Bean A在被注入到Bean B时还没有完成自己的初始化,但它已经是一个可以使用的对象,可以满足Bean B对其的依赖。随后,Bean A继续完成自身的初始化,确保最终两个Bean都处于完全初始化的状态。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值