本文主要是介绍Spring 依赖注入与循环依赖总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...
1. Spring 三级缓存解决循环依赖
✅ 适用场景:字段注入(@Autowired
直接加在字段上)或 Setter 注入(@Autowired
加在 Setter 方法上)。
✅ 解决流程:
- 一级缓存(
singletonObjects
):存放完全初始化好的单例 Bean。 - 二级缓存(
earlySingletonObjects
):存放提前暴露的半成品 Bean(已实例化但未初始化)。 - 三级缓存(
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
自注入)。 - 依次从一级、二级、三级缓存查找:
- 一级缓存:未找到(因为
UserService
未完全初始化)。 - 二级缓存:未找到。
- 三级缓存:找到
UserService
的ObjectFactory
,调用它生成对象。
- 一级缓存:未找到(因为
- Spring 开始初始化
- 生成代理对象:
- 工厂调用会创建
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
。 - 解决方案:
- 设置默认值(推荐):
@Value("${api.key:default-value}") private String apiKey; // 配置缺失时使用 "default-value"
- 允许
null
:
@Value("${api.key:#{null}}") private String apiKey; // 配置缺失时注入 nul
- 手动检查(复杂场景):
private String apiKey; public UserService(Environment env) { this.apiKey = env.getProperty("api.key", "default-value"); }
总结对比表
场景 | 能否解决循环依赖 | 推荐方案 |
---|---|---|
字段/Setter 注入 | ✅(三级缓存) | 可选依赖、快速开发编程 |
构造器注入 | ❌(直接报错) | 强制依赖、核心业务逻辑 |
@Value 注入配置 | 不涉及循环依赖 | 加默认值(${key:default} ) |
多例 Bean 循环依赖 | ❌(直接报错) | 避免多例 Bean 互相依赖 |
最佳实践:
- 优先用构造器注入(强制依赖 + 不可变性)。
- 循环依赖改用 Setter/字段注入 或
@Lazy
。 @Value
务必设置默认值,避免因配置缺失导致启动失败。
到此这篇关于Spring 依赖注入与循环依赖总结的文章就介绍到这了,更多相关Spring 依赖注入与循环依赖内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!
这篇关于Spring 依赖注入与循环依赖总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!