1. IoC与DI基础
1.1 什么是IoC
IoC(Inversion of Control):将对象的控制权交给 Spring 容器,由 Spring 帮你创建和销毁对象。
1.2 IoC如何解耦
以Computer类需要CPU属性为例:
| 方式 | 创建CPU实现类 | 更换实现类 |
|---|---|---|
| 手动创建 | 自己new IntelCPU() | 需要修改代码,改为new AMDCPU() |
| IoC方式 | 容器自动寻找并注入 | 只需向容器中注入其他实现类,无需改代码 |
IoC 的解耦本质:你不需要关心对象怎么创建、用哪个实现类,容器帮你搞定。
1.3 DI依赖注入
DI(Dependency Injection):给对象的属性赋值的过程就是依赖注入。
- 创建
Computer对象 → 把CPU对象注入进去 → 属性赋值完成 - 这个过程就是 DI
注意:因为注入的时候需要判断CPU类型,这里Spring区分CPU类型的方法如下
1. 先按类型 CPU 找到所有候选:intelCPU、amdCPU 2. 如果只有一个,直接用 3. 如果有多个,依次看: - @Qualifier 指定了谁 - 谁被 @Primary 标记了 - 字段名/参数名是否等于某个 Bean 名 4. 都不行就报 NoUniqueBeanDefinitionException2. 循环依赖问题
当 A 和 B 互相依赖时:
- A 有一个属性是 B → 创建 A 需要给它一个 B
- B 有一个属性是 A → 创建 B 需要给它一个 A
- 尝试创建 A → 发现需要 B → 尝试创建 B → 发现需要 A →死循环
这就是循环依赖问题,是 Spring IoC 需要解决的一个核心问题。
3. 二级缓存如何解决循环依赖
3.1 两级缓存的职责
| 缓存级别 | 存放内容 | 状态 |
|---|---|---|
| 一级缓存 | 完全初始化好的 Bean | 对象创建完成 + 属性已赋值 |
| 二级缓存 | 创建好但未初始化的 Bean | 对象创建完成 + 属性未赋值 |
3.2 二级缓存解决A-B循环依赖的流程
1. 创建A对象(new出来,属性未赋值)→ 放入二级缓存 2. 发现A依赖B → 去创建B对象(new出来,属性未赋值)→ 放入二级缓存 3. 发现B依赖A → 从二级缓存中找到A对象(未初始化但已存在) 4. B的属性A赋值完成 → B初始化完成 → 放入一级缓存 5. 回过头来,A依赖的B已就绪 → 从一级缓存拿到B → 注入给A 6. A的属性B赋值完成 → A初始化完成 → 放入一级缓存二级缓存通过提前暴露未完成初始化的对象,打破了循环依赖的死循环。
4. 为什么需要三级缓存
4.1 核心原因:AOP代理
Spring 有AOP(面向切面编程),在 AOP 场景中:
- 你拿到的不是对象本身,而是代理对象
- 二级缓存只能创建对象本身,无法搞定代理对象
如果只有二级缓存,在 AOP 场景下循环依赖就无法正确解决。
4.2 第三级缓存的作用
| 缓存级别 | 存放内容 | 作用 |
|---|---|---|
| 一级缓存 | 完全初始化好的 Bean | 供最终使用 |
| 二级缓存 | 未初始化的 Bean(对象本身或代理) | 提前暴露,打破循环 |
| 三级缓存 | 工厂对象(ObjectFactory) | 返回代理对象 |
第三级缓存放的是工厂对象,调用时会返回代理对象(而非原始对象本身)。
5. 三级缓存完整流程
5.1 A-B循环依赖的三级缓存解决流程
1. 创建A对象 → 把A的工厂对象放到三级缓存 2. 发现A依赖B → 创建B对象 → 把B的工厂对象也放到三级缓存 3. 发现B依赖A → 在一、二级缓存中都没找到A 4. 从三级缓存中拿出A的工厂对象 → 工厂对象返回A的代理对象 5. 把A(或A的代理)放到二级缓存(此时A属性未赋值) 6. 把A(或A的代理)注入到B → B属性赋值完成 7. B初始化完成 → 放入一级缓存 8. 回过头来,A依赖的B已就绪 → 从一级缓存拿到B → 注入给A 9. A属性赋值完成 → A初始化完成 → 放入一级缓存5.2 关键点
- 三级缓存存的是工厂对象,不是 Bean 实例本身
- 工厂对象调用时,会判断是否需要代理,返回代理对象
- 二级缓存中存放的可能是原始对象或代理对象
- 这样就保证了在 AOP 场景下,循环依赖中注入的是代理对象
6. 总结对比
6.1 二级 vs 三级缓存
| 对比项 | 二级缓存 | 三级缓存 |
|---|---|---|
| 能否解决普通循环依赖 | 能 | 能 |
| 能否解决AOP场景循环依赖 | 不能 | 能 |
| 第三级缓存存放内容 | — | 工厂对象(ObjectFactory) |
| 代理对象支持 | 不支持 | 支持 |
只有二级缓存: A 实例化 -> 放原始 a 到二级 -> B 注入原始 a -> A 初始化后创建代理 aProxy 结果:B 拿原始 a,容器拿 aProxy,不一致 三级缓存: A 实例化 -> 放 ObjectFactory 到三级 B 需要 A -> 调用 ObjectFactory -> getEarlyBeanReference 提前生成代理 aProxy -> 放 aProxy 到二级 -> B 注入 aProxy A 初始化 -> 发现早期引用已是 aProxy -> 最终暴露 aProxy 结果:B 和容器都是 aProxy,一致6.2 三级缓存各自职责
| 缓存 | 名称 | 内容 | 阶段 |
|---|---|---|---|
| 一级 | singletonObjects | 完全初始化好的 Bean | 可直接使用 |
| 二级 | earlySingletonObjects | 未初始化的 Bean(可能是代理) | 提前暴露,打破循环 |
| 三级 | singletonFactories | 工厂对象(ObjectFactory) | 生成代理对象 |
6.3 核心结论
二级缓存能解决普通循环依赖,但无法解决 AOP 场景下的循环依赖。三级缓存放的是工厂对象,能返回代理对象,这才是必须使用三级缓存的根本原因。