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

Spring解决循环依赖

一、什么是循环依赖?

循环依赖(Circular Dependency) 指两个或多个组件相互直接或间接依赖,形成闭环引用关系。在Spring框架中,主要表现为Bean之间的相互注入。

1. 循环依赖的三种典型形态

// 案例1:直接循环依赖(A↔B)
@Component
class A {
    @Autowired
    private B b;
}

@Component
class B {
    @Autowired
    private A a;
}

// 案例2:间接循环依赖(A→B→C→A)
@Component
class C {
    @Autowired
    private A a;
}

// 案例3:自我依赖(极端情况)
@Component
class Self {
    @Autowired
    private Self self; // 实际开发中应避免
}

2. 循环依赖的危害

  • 启动失败:Spring容器初始化时可能抛出BeanCurrentlyInCreationException
  • 代码耦合:破坏模块化设计原则
  • 调试困难:难以追踪依赖关系

二、Spring的解决之道:三级缓存机制

1. 三级缓存结构

Spring通过三级缓存解决单例Bean的循环依赖问题:

缓存名称数据结构存储内容
singletonObjects(一级缓存)ConcurrentHashMap完全初始化好的Bean
earlySingletonObjects(二级缓存)HashMap早期暴露对象(未完成属性注入)
singletonFactories(三级缓存)HashMap对象工厂(用于生成早期对象)

2. 核心解决流程

以A和B相互依赖为例:

Spring A B 三级缓存 二级缓存 一级缓存 开始创建A 将A的ObjectFactory放入三级缓存 发现需要注入B 开始创建B 将B的ObjectFactory放入三级缓存 发现需要注入A 通过A的ObjectFactory获取早期A 将早期A提升到二级缓存 完成B的初始化 注入完整的B 完成A的初始化 Spring A B 三级缓存 二级缓存 一级缓存

三、源码级解析

1. 核心源码位置

  • DefaultSingletonBeanRegistry:管理三级缓存
  • AbstractAutowireCapableBeanFactory:Bean创建主流程

2. 关键代码片段

// 创建Bean的核心方法
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化对象(此时对象属性为空)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 2. 将ObjectFactory加入三级缓存(解决循环依赖的关键)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
    // 3. 属性注入(可能触发依赖Bean的创建)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}

// 三级缓存写入逻辑
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

四、构造器注入 vs 属性注入

1. 构造器注入无法解决循环依赖

@Component
class C {
    private D d;
    @Autowired
    public C(D d) { this.d = d; } // 构造器注入
}

@Component
class D {
    private C c;
    @Autowired
    public D(C c) { this.c = c; } // 抛出BeanCurrentlyInCreationException
}

2. 原因分析

  • 构造器注入发生在实例化阶段,此时对象尚未创建完成,无法放入三级缓存
  • 属性注入发生在实例化之后,此时已通过三级缓存暴露早期引用

五、解决方案的局限性

1. 作用域限制

作用域类型是否支持循环依赖原因
Singleton(默认)三级缓存机制支持
Prototype每次获取新对象,无法提前暴露
Request/Session作用域生命周期短且不固定

2. 代理对象问题

  • 若Bean需要AOP代理,必须使用CGLIB代理(JDK代理需要接口)
  • 解决方法:配置spring.aop.proxy-target-class=true

六、开发建议与最佳实践

1. 避免循环依赖的方法

  • 模块化设计:遵循单一职责原则
  • 依赖方向控制:保持单向依赖(A→B→C)
  • 接口抽象:通过接口解耦具体实现
  • 延迟加载:使用@Lazy注解
@Component
class E {
    @Lazy // 延迟注入
    @Autowired
    private F f;
}

2. 必须使用循环依赖时

  1. 确保使用属性注入(setter/字段注入)
  2. 所有相关Bean必须为单例
  3. 避免在@PostConstruct方法中访问依赖Bean

七、终极解决方案:重新设计

当遇到复杂的循环依赖时,应考虑以下重构方案:

重构方法实施案例
引入第三方类将公共逻辑提取到新类C,让A、B都依赖C
使用事件驱动通过ApplicationEvent解耦
转换为方法调用使用ApplicationContext.getBean()
接口分层定义服务接口与实现类分离

总结

Spring通过三级缓存机制巧妙地解决了单例Bean的循环依赖问题,但开发者应认识到:

  1. 这是框架层面的补救措施,而非设计推荐
  2. 构造器注入的循环依赖无法解决
  3. 原型作用域的Bean不支持循环依赖
  4. 复杂项目应通过代码重构消除循环依赖
http://www.dtcms.com/a/99533.html

相关文章:

  • 【无人机路径规划】Matlab基于A星算法的无人机三维路径规划
  • maven高级
  • 微信小程序逆向开发
  • Java -jar运行jar包时可添加参数
  • ‌JVM 内存模型(JDK8+)
  • ModuleNotFoundError: No module named ‘demjson‘
  • JavaScript中的观察者模式
  • https://arxiv.org/
  • WebView展示广告对网站收益的影响分析(桌面平台非点击场景)
  • How to install OpenJ9 JDK 17 on Ubuntu 24.04
  • 《K230 从熟悉到...》矩形检测
  • 机器学习的一百个概念(2)Tomek 链接
  • 人工智能赋能医疗:开启智慧医疗新时代
  • Charles 抓包配置保姆教程(PC、IOS、Android)
  • 《强化学习基础概念:四大模型与两大损失》
  • 环境爬坑,切换python版本,让 jupyter 支持切换不同python版本内核
  • 家电产品智能屏方案,ESP32系列助力智能升级,物联网通信交互应用
  • CubeMx ——新建工程
  • python入门(一)个人笔记
  • 【二刷代码随想录】螺旋矩阵求解方法、推荐习题
  • 表格图表切换,图表无法展示问题复盘
  • 工作记录 2017-03-13
  • Python CSV 数据分析:问题排查与解决全记录
  • Windows学习笔记(3)关于事件日志
  • linux的文件
  • Visual Basic语言的折线图
  • Ubuntu 系统中,每日健康检查
  • vulnhub-Node1
  • C#:第一性原理拆解字段(fields)
  • ElasticSearch 分词器