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如何通过三级缓存(singletonObjectsearlySingletonObjectssingletonFactories)解决单例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年,确保技术栈的时效性。

相关推荐
如来神掌十八式2 小时前
Java所有的锁:从基础到进阶
java·
硅基诗人2 小时前
Java后端高并发核心瓶颈突破(JVM+并发+分布式底层实战)
java·jvm·分布式
沐知全栈开发2 小时前
JavaScript for 循环
开发语言
星空椰2 小时前
JavaScript 基础入门:从零开始掌握变量与数据类型
开发语言·前端·javascript·ecmascript
ulias2122 小时前
Linux中的开发工具
linux·运维·服务器·开发语言·c++·windows
聆听。。花开雨落2 小时前
intelij idea闪退后再启动tomcat报错端口冲突
java·tomcat·intellij-idea
Java面试题总结2 小时前
Spring Boot 包扫描新姿势:AutoScan vs @Import vs @ComponentScan 深度对比
java·数据库·spring boot
掘金者阿豪2 小时前
数据库安全第一关:用户密码存储与认证机制的深度拆解
java·前端·后端
花千树-0102 小时前
McpAgentExecutor 混合挂载:HTTP 工具与 NPX 服务器同时接入同一 Agent
java·agent·function call·spring ai·mcp·toolcall·java ai