Spring框架下的依赖注入坑:循环依赖与作用域混淆

前言

请添加图片描述

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:https://www.captainbed.cn/z
在这里插入图片描述
请添加图片描述


1. 错误场景复现

场景1:构造函数循环依赖

@Service
public class ServiceA {
    private final ServiceB serviceB;
    public ServiceA(ServiceB serviceB) { // 构造函数注入
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;
    public ServiceB(ServiceA serviceA) { // 构造函数注入
        this.serviceA = serviceA;
    }
}

后果:应用启动时抛出BeanCurrentlyInCreationException,提示循环依赖。


场景2:单例Bean中的原型Bean失效

@Scope("prototype")
@Service
public class PrototypeBean {
    private int count = 0;
    public void add() { count++; }
    public int getCount() { return count; }
}

@Service
public class SingletonBean {
    @Autowired
    private PrototypeBean prototypeBean; // 单例中注入原型Bean

    public void increment() {
        prototypeBean.add();
        System.out.println(prototypeBean.getCount()); // 每次输出递增,而非期望的1
    }
}

问题:单例Bean只初始化一次,导致注入的原型Bean实例被复用,失去“原型”特性。


场景3:多级循环依赖(A→B→C→A)

@Service
public class ServiceA {
    @Autowired private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired private ServiceC serviceC;
}

@Service
public class ServiceC {
    @Autowired private ServiceA serviceA; // 三级循环依赖
}

现象:Spring可能正常启动,但某些情况下(如AOP代理)仍会触发循环依赖异常。


2. 原理解析

Spring解决循环依赖的三级缓存

缓存层级存储内容作用阶段
一级缓存(singletonObjects)完全初始化的单例Bean应用运行期
二级缓存(earlySingletonObjects)提前暴露的未完成初始化的Bean(仅对象引用)Bean创建中
三级缓存(singletonFactories)Bean工厂,用于生成提前暴露的BeanBean实例化后、属性填充前
  • 构造函数注入无法解决循环依赖:Bean实例化前需先获取依赖对象,而构造函数注入必须在实例化时完成依赖注入,导致死锁。
  • Setter注入/字段注入可解决:实例化后通过反射注入依赖。

原型Bean在单例中的失效原因

  • 单例Bean初始化仅一次:依赖的原型Bean在初始化时注入,后续方法调用复用同一实例。
  • 解决方案:通过ApplicationContext实时获取,或使用@Lookup注解。

3. 正确解决方案

方案1:打破循环依赖

方法1.1:代码重构(优先推荐)
// 提取公共逻辑到第三个类
@Service
public class CommonService { /* 公共方法 */ }

@Service
public class ServiceA {
    private final CommonService commonService;
    public ServiceA(CommonService commonService) { ... }
}

@Service
public class ServiceB {
    private final CommonService commonService;
    public ServiceB(CommonService commonService) { ... }
}
方法1.2:使用@Lazy延迟加载
@Service
public class ServiceA {
    public ServiceA(@Lazy ServiceB serviceB) { // 延迟注入代理对象
        this.serviceB = serviceB;
    }
}

注意:@Lazy可能导致调试困难,需谨慎使用。


方案2:原型Bean的正确使用

方法2.1:ApplicationContext实时获取
@Service
public class SingletonBean {
    @Autowired
    private ApplicationContext context;

    public void increment() {
        PrototypeBean prototypeBean = context.getBean(PrototypeBean.class);
        prototypeBean.add();
        System.out.println(prototypeBean.getCount()); // 每次输出1
    }
}
方法2.2:使用ObjectFactory或Provider
@Service
public class SingletonBean {
    @Autowired
    private ObjectFactory<PrototypeBean> prototypeFactory;

    public void increment() {
        PrototypeBean prototypeBean = prototypeFactory.getObject();
        prototypeBean.add();
        System.out.println(prototypeBean.getCount());
    }
}
方法2.3:@Scope注解设置代理模式
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class PrototypeBean { ... }

// 单例Bean直接注入
@Service
public class SingletonBean {
    @Autowired 
    private PrototypeBean prototypeBean; // 每次调用时动态生成新实例
}

4. 工具与最佳实践

循环依赖检测工具

  1. Spring Actuator:
    启用beans端点,查看Bean依赖关系:

    management:
      endpoints:
        web:
          exposure:
            include: beans
    

    访问:http://localhost:8080/actuator/beans

  2. IDE插件:

    • IntelliJ的Spring插件可视化Bean依赖图
    • Eclipse的Spring Tools Suite

最佳实践

  • 优先使用构造函数注入:强制依赖明确,避免NullPointerException
  • 避免循环依赖:是设计问题而非技术问题,重构模块职责
  • 谨慎使用原型作用域:仅在需要状态隔离时使用(如用户会话)

5. Code Review检查清单

检查项正确做法
是否存在构造函数循环依赖?优先重构代码,其次使用@Lazy
原型Bean是否在单例中失效?检查是否使用ObjectFactory或@Scope代理
是否滥用字段注入?优先选择构造函数注入或Setter注入
是否误用@Primary注解?确保不会掩盖其他Bean的正确注入

6. 真实案例

某微服务项目因循环依赖导致启动失败:

  • 背景:订单服务(OrderService)与库存服务(StockService)相互构造函数注入。
  • 错误日志:Requested bean is currently in creation: Is there an unresolvable circular reference?
  • 修复步骤:
    1. 提取公共验证逻辑到独立模块(ValidationService)
    2. 将部分方法改为Setter注入
    3. 添加单元测试验证启动流程

结果:启动时间减少30%,模块职责更清晰。


总结

  • 循环依赖是设计警钟:重构优于技术规避
  • 作用域需精确控制:单例与原型混用要慎防状态污染
  • 注入方式影响扩展性:构造函数注入促进不可变性与清晰依赖
  • 工具辅助事半功倍:利用Spring生态工具快速定位问题

下期预告:《技术债累积警告:复制粘贴代码的连锁反应》——从代码重复率到维护成本,揭秘如何用设计模式和工具重构腐化代码。

联系作者

职场经验分享,Java面试,简历修改,求职辅导尽在科技泡泡
思维导图面试视频合集
在这里插入图片描述
在这里插入图片描述

一文读懂无人机AI识别算法,原理、技术、应用 本文详细介绍了无人机识别算法的工作原理、技术优势以及在多个行业的应用前景,展现了其在提升安全监控、资源管理和应急响应中的关键作用。尽管无人机识别算法在多个领域展现出巨大潜力,但仍面临技术挑战,如算法的准确性、数据的实时处理能力以及对复杂环境的适应性。4. 消防与城市管理: 无人机识别算法在消防和城市管理中的应用,为相关部门提供了及时的信息支持,提升了应急处置能力和城市管理水平。1. 公安监控: 无人机识别算法在公安领域的应用,可以实时识别犯罪嫌疑人和违法行为,有效提升城市治安管理的智能化水平。 阅读详情

相关推荐

PID控制器(位置式和增量式)

PID控制算法

zhinian_hao的博客 4512

机械臂TCP标定实战:从四点法到六点法的精度跃迁

本文详细解析机械臂TCP标定技术,从基础的四点法到高精度的六点法实现精度跃迁。通过工具坐标系与基坐标系的转换原理,结合实战案例展示六点法如何将装配合格率提升至99.5%,并分享环境控制、操作技巧等提升标定精度的关键要素。

weixin_29038345的博客 899

CASS3D实战:高效三维房屋与地形采集绘制技巧

本文详细介绍了CASS3D在三维房屋与地形采集绘制中的高效技巧,包括智能绘房功能、不规则结构处理、特殊构件快速绘制及地形数据采集策略。通过实战案例和进阶操作指南,帮助用户快速掌握CASS3D的核心功能,提升三维采集与绘制效率。

weixin_29216049的博客 870

抖音a_bogus参数逆向避坑指南:从环境检测到XHR请求的全流程解析

本文详细解析了抖音a_bogus参数在v1.0.1.19版本下的逆向工程全流程,重点介绍了如何通过PyV8Env框架进行有效的补环境操作,以绕过严格的环境检测。内容涵盖从环境初始化、关键事件触发到模拟XHR请求获取签名的完整步骤,并提供了可运行的Python代码示例和常见检测点的避坑指南,帮助开发者稳定生成有效的a_bogus参数。

tech5的博客 603

ROS Noetic + Realsense ROS 2.3.2 在鲁班猫4上的完整工作空间搭建与相机启动调试

本文详细介绍了在鲁班猫4(RK3588S)开发板上搭建ROS Noetic与Realsense ROS 2.3.2开发环境的完整流程,包括Realsense SDK安装、工作空间配置、D435i深度相机驱动调试等关键步骤,为机器人视觉系统开发提供实用指南。

weixin_33217202的博客 238

QT C++ AI棉花智能变量灌溉工上位机(信创完整版)

北斗履带灌溉机器人/田间分区电磁阀基站 → 多层土壤温湿度/盐分EC传感器实时采集墒情 → 双目AI视觉扫描棉田长势、病苗分布 → AI融合**生育期+土壤墒情+盐分+棉株长势**计算变量灌溉定额、水肥配比、灌溉时长 → 分区电磁阀/变频水泵变量供水,病弱苗地块减量、干旱地块增量;墒情、盐分、长势多因子闭环修正灌水量;多层土壤含水率(0–10/20–40/40–80cm)、土壤EC盐分、土壤温度、空气温湿度、蒸发量、北斗地块坐标、管道水压、水肥罐液位、分区流量计、水泵电机负载、滴灌带压力反馈。

寒寒豆豆软件公司的博客 188

Matlab仿真:均匀面阵方向图、均匀线阵方向图、均匀圆阵方向图

其中,阵列方向图是评价阵列性能的重要参数之一。均匀面阵由若干个相同的阵元构成,阵元间等距排列,阵元的方向图形状为一个圆盘。均匀线阵由若干个相同的阵元构成,阵元间等距排列,阵元的方向图形状为一个矩形。本文将采用Matlab进行仿真,分别实现均匀面阵方向图、均匀线阵方向图和均匀圆阵方向图,并对三种阵列进行比较分析。通过以上代码仿真,我们可以对均匀面阵、均匀线阵和均匀圆阵进行方向图的绘制,进一步分析不同阵列在天线性能方面的差异。Matlab仿真:均匀面阵方向图、均匀线阵方向图、均匀圆阵方向图。

2301_79326857的博客 3439

一、C++反作弊对抗实战 (基础篇 —— 2.注册表注入DLL)

在Windows NT/XP操作系统中,当需要加载user32.dll的程序启动时,user32.dll会加载注册表键HLM\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_dlls下面列出的所有模块。

Koma的主页 1098

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: 集合框架的暗坑:ArrayList遍历时删除元素的灾难
下一篇: 技术债累积警告:复制粘贴代码的连锁反应
雪碧有白泡泡
雪碧有白泡泡 优质创作者: 编程框架技术领域 优质创作者: 编程框架技术领域
博客等级 码龄5年 1万+粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪碧有白泡泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值