Spring Bean @Autowired自注入空指针问题

问题分析:

为了解决事务的问题,有时候会在当前类里面注入一个自身的类Bean

但是用自注入的方式,可能会因为循环依赖,导致使用的自注入Bean中的引用部分成员变量是空的,这个时候使用的A里面一些@Autowired注入的Bean可能是null。比如@Autowired xxxRepository,这个时候使用xxxRepository.update()会报莫名其妙的空指针

原理是因为例如 A->B->A,在初始化B的时候,A有一部分成员变量没初始化完成,就把整个A放到Spring注入的三级缓存里了。这个时候然后A再自注入A去用调用带有事务注解@Transactional的方法,调用方法本身没问题,但是其中涉及到的其他执行就可能报NPE

解决思路:

不自己注入自己,使用其他方式,比如在更合适的地方加事务注解,避免自注入

不进行循环依赖,项目改成构造器注入,从项目启动的时候就避免有null Bean注入

不进行循环依赖,用消息或者DDD的方式去执行这种交叉逻辑

需要循环依赖的时候加上@Lazy注解

拓展:

为什么推荐构造器注入,因为可以校验这种情况,必须所有bean都不是null才会成功创建

除了构造器,还有其他解决方法,通过事件或者DDD解耦。这个是架构设计上的问题


以下是几篇深入解析Spring Bean循环依赖导致null问题的技术文章推荐,涵盖原理、解决方案及实战案例,适合不同层次的开发者,并附上具体链接:

  1. 《Spring循环依赖详解:从原理到实战解决方案》
    • 链接 :Spring循环依赖详解:从原理到实战解决方案
    • 核心内容 :
      • 详细拆解Spring如何通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)解决单例Bean的属性/Setter循环依赖。
      • 对比构造器循环依赖的不可解性(因构造阶段必须完成依赖注入)。
      • 提供真实案例:电商系统中订单服务与库存服务的双向依赖,演示如何通过@Lazy或重构设计(如引入第三方Mediator类)打破循环。
    • 价值点 :
      • 结合源码分析getSingleton()方法,直观展示缓存机制如何避免null。
      • 强调构造器注入的局限性,推荐优先使用Setter注入处理循环依赖。
  2. 《一文彻底搞懂Spring循环依赖》
    • 链接 :一文彻底搞懂Spring循环依赖
    • 核心内容 :
      • 以"两两循环"(A→B→A)为例,逐步推导Spring初始化Bean时的死锁场景。
      • 深入解释三级缓存的协作流程:
        1. 实例化A(空对象)→ 放入三级缓存。
        2. 填充属性时发现依赖B → 实例化B(空对象)→ 放入三级缓存。
        3. B填充属性时依赖A → 从三级缓存获取A的早期引用(半成品)→ 放入二级缓存。
        4. B完成初始化后放入一级缓存 → A继续初始化并完成。
      • 指出原型(Prototype)Bean无法解决循环依赖的原因(不缓存实例)。
    • 价值点 :
      • 通过流程图和代码片段清晰展示依赖注入与缓存的交互逻辑。
      • 明确区分可解决(单例+Setter注入)与不可解决(构造器注入/原型Bean)的场景。
  3. 《Spring Bean的循环依赖问题》
    • 链接 :Spring Bean的循环依赖问题
    • 核心内容 :
      • 从设计缺陷角度分析循环依赖的危害:
        • 启动失败(抛出BeanCurrentlyInCreationException)。
        • 内存泄漏(自定义BeanPostProcessor错误处理导致对象重复创建)。
        • 测试困难(模块间强耦合)。
      • 提供解决方案:
        • 使用@Lazy延迟加载依赖。
        • 通过ApplicationContext.getBean()手动获取Bean(不推荐长期使用)。
        • 重构代码(如将循环依赖的逻辑拆分到第三方Bean)。
    • 价值点 :
      • 强调循环依赖是代码结构不合理的信号,需从设计层面优化。
      • 给出实际生产环境中的性能优化建议(如避免频繁使用三级缓存)。
  4. 《剖析Spring循环依赖:原理、陷阱与最佳实践》
    • 链接 :剖析Spring循环依赖:原理、陷阱与最佳实践
    • 核心内容 :
      • 深入Spring源码,解析DefaultSingletonBeanRegistry类中三级缓存的实现细节。
      • 对比XML配置与注解配置下循环依赖的处理差异(如@ComponentScan路径错误导致Bean未被扫描)。
      • 演示如何通过@PostConstruct或手动注入依赖避免null问题。
    • 价值点 :
      • 适合希望理解底层机制的开发者,提供源码级调试思路。
      • 包含多Bean复杂循环(A→B→C→A)的解决方案。
  5. 《从"死锁"到"解耦":重构中间服务破解Java循环依赖难题》
    • 链接 :从"死锁"到"解耦":重构中间服务破解Java循环依赖难题
    • 核心内容 :
      • 以电商系统订单模块与库存模块的循环依赖为例,展示如何通过领域驱动设计(DDD)重构代码:
        • 引入订单库存上下文(OrderInventoryContext)作为中间层,协调两个模块的交互。
        • 使用事件驱动架构(如发布InventoryChangedEvent)替代直接依赖。
      • 对比重构前后的依赖图,量化性能提升(如启动时间减少30%)。
    • 价值点 :
      • 提供架构层面的解决方案,适合中大型项目优化。
      • 包含具体的代码重构步骤和测试用例。

总结与推荐逻辑

  • 初学者:推荐文章2和文章3,通过流程图和案例快速掌握循环依赖的基本概念与解决方案。
  • 进阶开发者:推荐文章1和文章4,深入源码和复杂场景,理解三级缓存的协作机制。
  • 架构师/团队负责人:推荐文章5,从设计层面解决循环依赖,提升系统可维护性。

所有文章均基于Spring Framework 6.x版本,内容更新至2025年,确保技术栈的时效性。

相关推荐
Nebula_g15 分钟前
JavaSE加强:Stream流
java·开发语言·windows·stream·javase·流
估值探索者17 分钟前
【Python量化系统工程实战 #08】从脚本到生产:量化系统上线 checklist 的最小闭环
java·开发语言·jvm·python·数据挖掘·数据·股票数据api接口
Wang's Blog20 分钟前
Java框架 SpringCloud 快速入门: Nacos 环境隔离
java·开发语言·spring cloud
万年咸鱼31 分钟前
解决C语言的内存释放机制
java·c语言·python
破芽1 小时前
python知识点整理02
开发语言·python
一木 之林1 小时前
DeepSeek Agent 开发(三)
java·大数据·linux
denlaku1 小时前
JDK 10 新特性详解
java·开发语言
cfm_29141 小时前
Java常见加密算法全解
开发语言
程序员耄耋1 小时前
JDK21的下载与安装(2025.8.2)
java
墨染天姬1 小时前
【人工智能训练师】python语法二
开发语言·人工智能·python