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

Spring 如何解决循环依赖以及那些无法解决的循环依赖

在 Spring 框架的开发过程中,循环依赖是一个常见且需要谨慎处理的问题。今天咱们就来深入探讨一下 Spring 是如何解决循环依赖的,以及哪些情况下循环依赖是无法解决的。

一、什么是循环依赖

循环依赖,简单来说,就是两个或两个以上的 bean 相互持有对方的引用,最终形成一个闭环。比如,A bean 依赖 B bean,而 B bean 又依赖 A bean,这就像两个人互相拉着对方的手,谁也没办法先迈出第一步。在 Spring 中,注入 bean 的方式主要有构造器注入和 field 属性注入,不同的注入方式在面对循环依赖时表现各异。

二、Spring 如何解决 field 属性注入的循环依赖

Spring 解决 field 属性注入循环依赖的核心机制是三级缓存和 Java 的引用传递,同时采用了提前暴露对象和延后设置属性的策略。

假设我们有两个类 A 和 B,它们通过 field 属性注入形成了循环依赖:

收起

java

import org.springframework.stereotype.Service;

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

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

当 Spring 容器启动创建 bean 时,大致过程如下:

  1. 创建 A 对象并放入三级缓存:Spring 首先通过反射创建 A 对象的实例,这个时候 A 对象还不完整,它的 b 属性还是 null。但 Spring 会把这个 “半成品” A 对象提前暴露到三级缓存(singletonFactories)中。
  2. 触发 B 对象的创建:A 对象需要填充 b 属性,于是去缓存中查找 B 对象。由于此时 B 对象还未创建,就会触发 B 对象的创建流程。
  3. 创建 B 对象并放入三级缓存:B 对象创建后也被放入三级缓存,此时 B 对象的 a 属性同样为空。
  4. 完成 B 对象的属性填充:B 对象在填充 a 属性时,会从缓存中找到之前提前暴露的 A 对象,并完成注入。
  5. 完成 A 对象的属性填充:最后,A 对象从缓存中顺利获取到 B 对象,完成 b 属性的填充。至此,循环依赖问题得以解决。

这里的三级缓存各司其职:

  • 一级缓存(singletonObjects:存放的是完整的、可以直接使用的 bean。从这里取出的 bean,就像是装配好所有零件、可以直接投入使用的机器。
  • 二级缓存(earlySingletonObjects:存放提前暴露的 bean,但这些 bean 还不完整,尚未完成属性注入和执行 init 方法,主要用于解决循环依赖。它就像是半成品,虽然有了大致的框架,但还缺少一些关键的部分。
  • 三级缓存(singletonFactories:对初始化后的 bean 完成 AOP 代理操作。它保证了 bean 的生命周期,确保在合适的时机生成代理,而不是在实例化之后就立即生成,避免影响 bean 的正常功能。

三、Spring 如何解决构造器注入的循环依赖

构造器注入的循环依赖相对复杂一些,Spring 无法像处理 field 属性注入那样直接解决,需要借助一些额外的手段。常见的解决方式有两种:使用@ScopeproxyMode属性和@Lazy注解。

(一)使用 @Scope 的 proxyMode 属性

收起

java

import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

@Service
public class A {
    private B b;

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

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class B {
    private A a;

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

@Scope注解用于定义 bean 的作用域,默认是单例(singleton)。通过proxyMode属性可以设置类的代理模式,这里使用ScopedProxyMode.TARGET_CLASS表示使用 CGLIB 动态代理。这种方式下,Spring 通过创建代理对象打破循环依赖,使得 A 和 B 能够顺利创建。

(二)使用 @Lazy 注解

收起

java

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Lazy;

@Service
public class A {
    private B b;

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

@Service
public class B {
    private A a;

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

@Lazy注解的作用是让 bean 延迟加载。当 A 依赖 B 时,由于 B 被标记为延迟加载,Spring 会创建一个 B 的代理对象 Bproxy 来满足 A 的依赖。这样,A 依赖的是 Bproxy,而不是真实的 B 对象,打破了循环依赖。之后创建 B 时,因为 A 已经存在,B 也能成功创建。

四、无法解决的循环依赖

虽然 Spring 提供了多种解决循环依赖的方法,但有些情况下循环依赖确实无法解决。

最典型的就是构造器注入的循环依赖,如果没有使用上述提到的特殊手段(@ScopeproxyMode属性或@Lazy注解),Spring 在启动时会抛出BeanCurrentlyInCreationException异常。这是因为构造器注入要求在创建 bean 时就完成所有依赖的注入,而循环依赖使得这个过程陷入了死锁,Spring 无法确定先创建哪个 bean。

另外,在一些复杂的自定义 BeanPostProcessor 场景中,如果自定义的后置处理器逻辑与循环依赖相互干扰,也可能导致循环依赖问题无法解决。比如,后置处理器在 bean 初始化过程中对依赖关系进行了错误的修改,使得 Spring 原本的解决机制失效。

五、总结

Spring 通过三级缓存机制巧妙地解决了 field 属性注入的循环依赖问题,同时借助动态代理(@ScopeproxyMode属性和@Lazy注解)来处理构造器注入的循环依赖。然而,我们也要清楚地认识到,并非所有的循环依赖都能被 Spring 轻松化解。在实际开发中,合理设计 bean 之间的依赖关系,避免不必要的循环依赖,才是编写健壮 Spring 应用的关键。希望通过这篇博客,大家对 Spring 的循环依赖问题有更深入的理解,在开发中能够更加游刃有余地应对相关挑战。

相关文章:

  • 【贝克街迷宫疑云:用侦探思维破解Java迷宫算法】
  • java练习(26)
  • 《open3d pyqt》Alpha重建
  • el-table使用fixed=“right“时固定多列,最左边的左边框不显示
  • C++常用数学函数详解与代码示例
  • C++ ——static关键字
  • ubuntu20.04安装nccl2.16.5
  • 物联网平台建设方案
  • 直线模组在搬运过程中需要注意什么?
  • 网络安全扫描--基础篇
  • Flowith.io 初探:DeepSeek-R1免费用,用画布式 AI 提升效率和创意
  • Deepseek部署-本地windows非系统C盘 -可视化 -4060甜品卡
  • Pikachu靶场-SSRF漏洞
  • DeepSeek视角下学术论文创新点探索干货分享!
  • 详述发票二维码与python解析技术
  • 链表和list
  • 情书网源码 情书大全帝国cms7.5模板
  • Vue 记录用户进入页面的时间、离开页面的时间并计算时长
  • 【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑤】
  • 网工项目理论1.7 设备选型
  • 电子商务网站建设的方法/外贸推广如何做
  • 石家庄高端网站建设/seo专业培训班
  • 岳阳网站制作/网络营销的现状和发展趋势
  • 个人做门户网站需要注册/百度搜索链接入口
  • 自己做盗版小说网站吗/seo销售是做什么的
  • 旅游 网站建设目标/广告服务平台