Spring 依赖注入与循环依赖总结

2025-08-21 22:50

本文主要是介绍Spring 依赖注入与循环依赖总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...

1. Spring 三级缓存解决循环依赖

适用场景:字段注入(@Autowired 直接加在字段上)或 Setter 注入(@Autowired 加在 Setter 方法上)。

解决流程

  1. 一级缓存(singletonObjects):存放完全初始化好的单例 Bean。
  2. 二级缓存(earlySingletonObjects):存放提前暴露的半成品 Bean(已实例化但未初始化)。
  3. 三级缓存(singletonFactories):存放生成 Bean 的 ObjectFactory(用于延迟创建代理对象)。  关键点:Spring 先创建 Bean 的实例(调用无参构造器),再通过缓存逐步解决依赖,避免死锁。

举例:

个人理解就是,以UserService为例,先创建UserService单例实例放在一级缓存中,如果检测到如@Transition等注解,将单例实例包装成工厂存到三级缓存中,到UserService初始化时发现自注入,分别从一二三级缓存中查找(告诉我查找什么),发现三级缓存中包含UserService 的工厂,然后调用改工厂生成代理对象,并且该代理对象中有对UserService为原始对象的引用,而不是对代理对象的引用,然后生成代理对象存到二级缓存中,并注入到原始对象自注入的UserService中(此时原始对象不是有对代理对象的引用吗?),然后继续完成初始化如@PostConstruct,然后将代理对象放到一级缓存中(此时原始对象还在吗?),最后清楚二级三级缓存中的UserService代理对象和工厂。

你的理解基本正确,但有些细节需要澄清和补充。让我们以 UserService 为例,梳理 Spring 解决循环依赖(自注入)的完整流程,并明确每一步缓存的状态和对象引用关系。

1. 创建UserService原始对象

  • 动作:Spring 调用 UserService 的构造函数,创建一个原始对象(非代理)。
  • 缓存状态
    • 一级缓存(单例池):无 UserService
    • 二级缓存(早期暴露对象):无&androidnbsp;UserService
    • 三级缓存(对象工厂):无 UserService

2. 将原始对象包装成工厂,存入三级缓存

  • 动作
    • 检测到 UserService 需要代理(如被 @Transactional 标记),Spring 将原始对象包装成一个 ObjectFactory 工厂,存入三级缓存。
    • 此时对象尚未初始化(未填充属性、未执行 @PostConstruct)。
  • 缓存状态
    • 一级缓存:无 UserService
    • 二级缓存:无 UserService
    • 三级缓存UserService 的 ObjectFactory

3. 处理UserService的自注入

  • 动作
    • Spring 开始初始化 UserService,发现它依赖自身(如通过 @Autowired 自注入)。
    • 依次从一级、二级、三级缓存查找:
      1. 一级缓存:未找到(因为 UserService 未完全初始化)。
      2. 二级缓存:未找到。
      3. 三级缓存:找到 UserService 的 ObjectFactory,调用它生成对象。
  • 生成代理对象
    • 工厂调用会创建 UserService 的代理对象(如 JDK 动态代理或 CGLIB 代理)。
    • 关键点:代理对象内部持有对原始对象的引用(即 target 属性指向原始对象)。
  • 缓存更新
    • 将代理对象存入二级缓存(此时原始对象仍在创建中)。
    • 三级缓存中的工厂不会被移除(直到 UserService 完全初始化)。
  • 注入依赖
    • 将代理对象注入到原始对象的自依赖字段中(此时原始对象持有代理对象的引用)。

4. 继续初始化原始对象

  • 动作
    • 完成属性填充(如其他依赖注入)。
    • 执行 @PChina编程ostConstruct 初始化方法。
    • 此时原始对象已完全初始化,但仍是原始对象(未被代理替换)。
  • 缓存状态
    • 一级缓存:无 UserService(因为尚未放入)。
    • 二级缓存UserService 的代理对象。
    • 三级缓存UserService 的 ObjectFactory

5. 将代理对象提升到一级缓存

  • 动作
    • Spring 将二级缓存中的代理对象移动到一级缓存(单例池)。
    • 清除二级缓存和三级缓存中的 UserService 相关条目。
  • 缓存状态
    • 一级缓存UserService 的代理对象。
    • 二级缓存:无 UserService
    • 三级缓存:无 UserService

关键问题解答

  • 原始对象的去向
    • 原始对象被代理对象持有(通过 target 属性引用),但对外暴露的永远是代理对象
    • 原始对象不会被销毁,它作为代理的目标对象长期存在。
  • 自注入的引用关系
    • 原始对象中注入的是代理对象(通过三级缓存的工厂提前暴露)。
    • 代理对象内部持有原始对象的引用(形成“代理 → 原始对象 → 代理”的引用链)。
  • 最终状态
    • 一级缓存中是代理对象,原始对象仅作为代理的目标存在。
    • 所有依赖 UserService 的 Bean 获得的都是代理对象。

理解误区:原始对象和代理对象之间相互引用就是循环依赖因为引用都是唯一的对象。

2. 无法用三级缓存解决的场景

构造器循环依赖(Constructor Circular Dependency):

@Service public class A { 
    private final B b; 
    public A(B b) { 
        this.b http://www.chinasem.cn= b; 
    } // 构造器依赖 B 
}
@Service public class B { 
    private final A a;
    public B(A a) { 
    this.a = a; 
    } // 构造器依赖 A 
}
  • 原因:构造器注入必须在实例化时完成依赖注入,无法提前暴露半成品 Bean。
  • 结果:直接抛出 BeanCurrentlyInCreationException

多例(Prototype)作用域的循环依赖

  • Spring 不处理多例 Bean 的循环依赖,直接报错。

3.@Autowired指定构造器注入

  • 默认行为:如果类只有一个构造器,Spring 会自动用它注入依赖。
  • 多构造器时:需要用 @Autowired 明确指定哪个构造器生效:
@Service 
public class UserService { 
    private final OrderService orderService; 
    @Autowired // 显式指定带参构造器 
    public UserService(OrderService orderService) { 
    this.orderService = orderService; 
    } 
    public UserService() {} // 无参构造器(默认不生效)
}
  • 优点:强制依赖、不可变(final 字段)、易于测试。

4.@Value注入配置

  • 默认行为:如果配置不存在,Spring 会抛出 IllegalArgumentException
  • 解决方案
    1. 设置默认值(推荐):
@Value("${api.key:default-value}") 
private String apiKey; // 配置缺失时使用 "default-value"
  1. 允许 null
@Value("${api.key:#{null}}") 
private String apiKey; // 配置缺失时注入 nul
  1. 手动检查(复杂场景):
private String apiKey; 
public UserService(Environment env) { 
    this.apiKey = env.getProperty("api.key", "default-value"); 
}

总结对比表

场景能否解决循环依赖推荐方案
字段/Setter 注入✅(三级缓存)可选依赖、快速开发编程
构造器注入❌(直接报错)强制依赖、核心业务逻辑
@Value 注入配置不涉及循环依赖加默认值(${key:default}
多例 Bean 循环依赖❌(直接报错)避免多例 Bean 互相依赖

最佳实践

  1. 优先用构造器注入(强制依赖 + 不可变性)。
  2. 循环依赖改用 Setter/字段注入 或 @Lazy
  3. @Value 务必设置默认值,避免因配置缺失导致启动失败。

到此这篇关于Spring 依赖注入与循环依赖总结的文章就介绍到这了,更多相关Spring 依赖注入与循环依赖内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于Spring 依赖注入与循环依赖总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155858

相关文章

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

SpringBoot请求参数传递与接收示例详解

《SpringBoot请求参数传递与接收示例详解》本文给大家介绍SpringBoot请求参数传递与接收示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录I. 基础参数传递i.查询参数(Query Parameters)ii.路径参数(Path Va

SpringBoot路径映射配置的实现步骤

《SpringBoot路径映射配置的实现步骤》本文介绍了如何在SpringBoot项目中配置路径映射,使得除static目录外的资源可被访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一... 目录SpringBoot路径映射补:springboot 配置虚拟路径映射 @RequestMapp

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

Java Stream 并行流简介、使用与注意事项小结

《JavaStream并行流简介、使用与注意事项小结》Java8并行流基于StreamAPI,利用多核CPU提升计算密集型任务效率,但需注意线程安全、顺序不确定及线程池管理,可通过自定义线程池与C... 目录1. 并行流简介​特点:​2. 并行流的简单使用​示例:并行流的基本使用​3. 配合自定义线程池​示

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Java Kafka消费者实现过程

《JavaKafka消费者实现过程》Kafka消费者通过KafkaConsumer类实现,核心机制包括偏移量管理、消费者组协调、批量拉取消息及多线程处理,手动提交offset确保数据可靠性,自动提交... 目录基础KafkaConsumer类分析关键代码与核心算法2.1 订阅与分区分配2.2 拉取消息2.3