找回密码
 立即注册
首页 业界区 业界 接着唠:三级缓存为啥是“刚需”?没有它Spring工厂得“ ...

接着唠:三级缓存为啥是“刚需”?没有它Spring工厂得“停工”!

但婆 2 小时前
上一篇咱们跟着“小A”机器人走完了单例Bean的“出生记”:从图纸(BeanDefinition)到搭骨架(实例化),发预订券(三级缓存),装零件(属性填充),测试调试(初始化),最后住进成品仓库(一级缓存)。
你可能会问:这三级缓存(工厂仓库、毛坯暂存处、成品仓库)看着挺复杂,为啥不直接简化成两级?或者干脆不用缓存,行不行? 今天咱们就掰扯掰扯:三级缓存到底是“锦上添花”还是“雪中送炭”?没有它,Spring工厂会变成啥样?
一、先回忆:三级缓存的“分工”与“活的/死的对象”

在上一篇里,三级缓存像个“临时应急系统”,咱们用“机器人组装厂”的仓库布局图“活的/死的对象”比喻来回顾:
  1. ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐  
  2. │  一级缓存        │     │  二级缓存        │     │  三级缓存        │  
  3. │  (成品仓库)      │◄────┤  (毛坯暂存处)    │◄────┤  (工厂仓库)      │  
  4. │  singletonObjects│     │  earlySingletonObjs│     │  singletonFactories│  
  5. │  存“完全体”机器人│     │  存“活的毛坯”    │     │  存“预订券”      │  
  6. │  (测试合格)      │     │  (动态引用)      │     │  (ObjectFactory) │  
  7. └─────────────────┘     └─────────────────┘     └─────────────────┘  
  8.        ▲                       ▲                       ▲  
  9.        │                       │                       │  
  10.        └── 成品入库(清除二三级)┘                       │  
  11.        │                                               │  
  12.        └── 活的毛坯升级(三级→二级→一级)┘              │  
  13.        │                                               │  
  14.        └── 发预订券(实例化后→三级)┘  
复制代码

  • 三级缓存(工厂仓库):存“预订券”(ObjectFactory),承诺“谁急着用毛坯,拿券来换”。
  • 二级缓存(毛坯暂存处):存“活的毛坯”(三级缓存的二级缓存)——指向原始Bean的动态引用(属性随创建同步更新,和最终成品是同一个对象)。
  • 一级缓存(成品仓库):存“完全体”机器人(测试合格,随时能领)。
关键对比:如果只用两级缓存(成品库+毛坯暂存处),二级缓存存的是“死的毛坯”——提前生成的独立副本(像“静态照片”,属性不更新,和最终成品是两个对象)。
二、三级缓存的“必要性”:用“活的”对象破解“死的”困局

1. 避免“无用功”:没循环依赖时,别生成“死的”副本!

假设工厂只搞两级缓存(成品库+毛坯暂存处),会发生啥?
场景:造“小A”机器人(无循环依赖,不需要AOP代理)。

  • 两级缓存逻辑:实例化小A→立刻生成“死的毛坯”(原始对象副本,像“提前拍的空箱子照片”)→放进二级缓存。但小A没被依赖,这个“死副本”永远用不上,白占内存
  • 三级缓存逻辑:实例化小A→发“预订券”到三级缓存(不生成对象)。没循环依赖?“预订券”躺仓库里啥也不干,省资源
“死的”副本本质:两级缓存的二级缓存是提前生成的独立副本(复印件),和后续创建的Bean“脱钩”,属性永远是new出来的瞬间状态(比如null)。
对比图
  1. 两级缓存(死副本浪费版)               三级缓存(活引用省心版)  
  2. ┌─────────────┐                ┌─────────────┐  
  3. │ 实例化小A    │                │ 实例化小A    │  
  4. ├─────────────┤                ├─────────────┤  
  5. │ 生成“死副本”→二级缓存 │ (无用功!)    │ 发“预订券”→三级缓存 │ (啥也不干)  
  6. │ (属性null,永远不变)│                │ (只存“取件承诺”)  │  
  7. ├─────────────┤                ├─────────────┤  
  8. │ 装零件→无依赖  │                │ 装零件→无依赖  │  
  9. ├─────────────┤                ├─────────────┤  
  10. │ 成品→一级缓存  │                │ 成品→一级缓存  │  
  11. └─────────────┘                └─────────────┘  
复制代码
2. 处理AOP代理:别让“死的半成品贴膜”坑了自己!

AOP代理(比如给机器人“贴膜”加日志)得等零件装得差不多了再贴,不然容易贴歪。
两级缓存的“死对象”坑

  • 实例化小A→立刻生成“死的毛坯”(原始对象)→当场贴膜(生成代理对象,像“给空箱子拍张带膜的照片”)→放进二级缓存。
  • 此时小A的零件还没装(属性null),代理对象(“死贴膜”)里的属性永远是null!后续小A装零件时,“死贴膜”不会更新,用的时候必然报空指针。
三级缓存的“活对象”巧

  • 实例化小A→发“预订券”到三级缓存(不贴膜)。
  • 发生循环依赖时(比如小B急着要小A),拿券现场生成“活的毛坯”(指向原始小A的动态引用,像“带零件的空箱子本身”)→按需贴膜(此时零件已填充一部分)→放进二级缓存。
  • “活的毛坯”属性随小A后续装零件同步更新(因为是同一个对象),代理对象(“活贴膜”)始终有效。
“活的”vs“死的”贴膜对比图
  1. 两级缓存(死贴膜:先贴膜再装零件)      三级缓存(活贴膜:先装零件再按需贴膜)  
  2. ┌─────────────┐                        ┌─────────────┐  
  3. │ 实例化小A    │                        │ 实例化小A    │  
  4. ├─────────────┤                        ├─────────────┤  
  5. │ 生成原始对象  │                        │ 发“预订券”   │  
  6. ├─────────────┤                        ├─────────────┤  
  7. │ 立刻贴膜→“死代理” │ (属性null,永远不变)  │ 发生循环依赖→拿券生成“活毛坯” │  
  8. │ (死对象:静态照片)│                        │ (活对象:动态引用,属性更新)│  
  9. ├─────────────┤                        ├─────────────┤  
  10. │ 死代理→二级缓存  │ (膜贴歪的半成品)     │ 按需贴膜→活代理→二级缓存    │  
  11. ├─────────────┤                        ├─────────────┤  
  12. │ 装零件(属性填充)│ (死代理不更新)       │ 装零件(活代理属性同步更新)  │  
  13. └─────────────┘                        └─────────────┘  
复制代码
3. 解决循环依赖死锁:没有“活的”对象,工厂直接“停工”!

这是三级缓存最核心的价值。咱们用“小A”(需AOP代理)和“小B”(依赖小A)循环依赖的例子,对比“死的”对象“活的”对象的后果:
场景1:两级缓存(死对象导致崩溃)
  1. 造小A → 实例化→生成“死代理”(属性null,像空箱子照片)→二级缓存(死对象)  
  2.        ↓  
  3. 造小B → 实例化→装零件要小A→拿小A“死代理”(属性null)装上  
  4.        ↓  
  5. 小B造完→一级缓存(小B手里的小A是“死代理”,属性null→用时空指针)  
  6.        ↓  
  7. 小A继续装零件→生成新代理(属性满)→一级缓存(新代理和死代理是两个对象,单例破坏)  
复制代码
场景2:三级缓存(活对象圆满解决)
  1. 造小A → 实例化→发“预订券”到三级缓存 → 装零件要小B → 造小B  
  2.        ↓                              ↓  
  3. 造小B → 实例化→发“预订券”到三级缓存 → 装零件要小A→拿小A券→生成“活毛坯”(动态引用,属性随小A更新)→二级缓存(活对象)  
  4.        ↓                              ↓  
  5. 小B装上小A“活毛坯” → 小B造完→一级缓存(小B手里的小A是“活对象”,属性会更新)  
  6.        ↓  
  7. 小A拿到小B成品→装完零件→初始化→成品→一级缓存(小A活毛坯升级为成品,和小B手里的是同一个对象)  
复制代码
“活的”对象本质:三级缓存的二级缓存是指向原始Bean的动态引用(原件链接),和最终成品是同一个对象,属性随创建同步更新,永远不会“空”。
4. 确保单例唯一性:别让“死的”副本和“活的”成品打架!

单例Bean要求“整个工厂只有一个”,两级缓存的“死对象”(独立副本)会导致“毛坯”和“成品”并存(比如小B手里是小A死副本,成品库是小A新成品),违反单例。
三级缓存的“活对象”保证:通过“预订券→活毛坯→成品”的单向转移,确保每个Bean在任意时刻只在一个缓存里(成品库优先,其次是活毛坯,最后是预订券),活对象始终指向同一个原件,单例唯一。
三、总结:“活的”对象是三级缓存的灵魂

看到这儿你应该明白了:三级缓存的核心是用“活的”对象(动态引用)替代了两级缓存的“死的”对象(静态副本)

  • “死的”对象(两级缓存):提前生成的独立副本,属性不更新,和成品是两个对象,导致内存浪费、代理无效、单例破坏。
  • “活的”对象(三级缓存):指向原始Bean的动态引用,属性同步更新,和成品是同一个对象,高效、正确、安全。
就像咱们工厂里的毛坯机器人:“死的”是提前拍的照片(永远空壳),“活的”是留在工位上的原件(边装零件边变完整)。三级缓存就是那个“让原件边装边借”的聪明系统——没循环依赖时省资源,有循环依赖时“活的”对象顶上,保证生产线不停!
所以啊,下次再看到singletonFactories、earlySingletonObjects、singletonObjects,想想“预订券”“活毛坯”“成品库”,就知道Spring为啥这么设计了——一切都是为了让你写的代码,能顺顺利利跑起来
(完)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册