📌 PDF :AI人工智能 --- AI Agent智能体实战项目
大白话说Java设计模式-41-备忘录模式(源码剖析篇):JDK / Spring 中的备忘录实现
📌 一句话本质:备忘录在源码里不是"保存状态"的形象化,而是"快照 + 恢复 + 事务"三件套的工业级架构。
🏷️ 标签:备忘录模式 / StateMachine / Serializable / 事务 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师
目录
- 一、为什么读备忘录源码?一张地图先看清
- [二、Spring 源码剖析:StateMachine 状态备忘录](#二、Spring 源码剖析:StateMachine 状态备忘录)
- [三、JDK 源码剖析:Serializable 序列化备忘录](#三、JDK 源码剖析:Serializable 序列化备忘录)
- 四、数据库事务:事务回滚备忘录
- 五、为什么框架作者这么设计?
- [六、借鉴到我们项目:3 个"抄作业"实践](#六、借鉴到我们项目:3 个"抄作业"实践)
- [七、本篇小结 + 下一模式预告](#七、本篇小结 + 下一模式预告)
一、为什么读备忘录源码?一张地图先看清
备忘录模式在 JDK 和 Spring 里出现得广泛 ------StateMachine 持久化 / Serializable 序列化 / 事务回滚都是备忘录思想。
源码地图:
| 类别 | 框架 / 中间件 | 备忘录实现 | 用途 |
|---|---|---|---|
| Spring | statemachine |
StateMachinePersist |
状态机持久化 |
| Spring | transaction |
TransactionSynchronizationManager |
事务快照 |
| JDK | java.io |
Serializable |
序列化 |
| MyBatis | org.apache.ibatis.executor |
Executor |
一级缓存(事务内) |
| 业务 | 自定义 | Memento |
撤销/重做 |
我们按"StateMachine → Serializable → 事务"的顺序来剖析。
二、Spring 源码剖析:StateMachine 状态备忘录
2.1 StateMachinePersist:备忘录接口
源码位置:
- Spring Statemachine 4.0.x
spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachinePersist.java
核心源码:
java
/**
* ✅ 状态备忘录
*/
public interface StateMachinePersist<S, E, T> {
/**
✅ 写备忘录
*/
void write(StateMachineContext<S, E> context, T contextObj) throws Exception;
/**
* 读备忘录
*/
StateMachineContext<S, E> read(T contextObj) throws Exception;
}
关键点:
| 关键点 | 解释 |
|---|---|
write() |
保存快照 |
read() |
恢复快照 |
StateMachineContext |
状态机上下文(备忘录) |
T contextObj |
标识(如 orderId) |
2.2 DefaultStateMachineContext:默认备忘录
核心源码:
java
/**
* ✅ 默认备忘录实现
*/
public class DefaultStateMachineContext<S, E> implements StateMachineContext<S, E> {
private final S state;
private Set<E> events;
private Map<String, Object> variables;
public DefaultStateMachineContext(S state) {
this(state, null, null, null);
}
public DefaultStateMachineContext(S state, Set<E> events,
Map<String, Object> variables, ...) {
this.state = state;
this.events = events;
this.variables = variables;
}
@Override
public S getState() {
return state;
}
}
关键点:
| 关键点 | 解释 |
|---|---|
state |
当前状态 |
events |
待处理事件 |
variables |
状态变量 |
| 不可变 | 备忘录不可修改 |
2.3 RedisStateMachinePersist:Redis 持久化
完整实现:
java
/**
* ✅ Redis 持久化备忘录
*/
public class RedisStateMachinePersist implements StateMachinePersist<String, String, String> {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void write(StateMachineContext<String, String> context, String id) throws Exception {
// ✅ 备忘录存 Redis
String state = context.getState() == null ? null : context.getState().toString();
redisTemplate.opsForValue().set("sm:" + id, state);
}
@Override
public StateMachineContext<String, String> read(String id) throws Exception {
// ✅ 读备忘录
String state = redisTemplate.opsForValue().get("sm:" + id);
return new DefaultStateMachineContext<>(state);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| Redis 存储 | 持久化 |
sm:{id} |
Key 格式 |
| 状态恢复 | 重启后从 Redis 读 |
2.4 完整状态机持久化调用链
StateMachine.stop()
↓
StateMachinePersist.write(context, id)
↓
Redis 写入
↓
StateMachine.start()
↓
StateMachinePersist.read(id)
↓
DefaultStateMachineContext
↓
stateMachine.stop(); stateMachine = restoreState; stateMachine.start();
2.5 StateMachinePersist 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 接口分离 | Persist / Context | 单一职责 |
| 多种实现 | Redis / DB / Memory | 灵活 |
| 不可变 | DefaultStateMachineContext | 备忘录安全 |
| 异步持久化 | TaskExecutor | 性能 |
三、JDK 源码剖析:Serializable 序列化备忘录
3.1 Serializable:序列化接口
源码位置:
- OpenJDK 17
src/java.base/share/classes/java/io/Serializable.java
核心源码:
java
/**
* ✅ 序列化标记接口
*/
public interface Serializable {
// 标记接口,无方法
}
关键点:
| 关键点 | 解释 |
|---|---|
| 标记接口 | 无方法 |
| 自动序列化 | JVM 处理 |
serialVersionUID |
版本控制 |
transient |
不序列化 |
3.2 ObjectOutputStream:备忘录写入
核心源码(简化版):
java
/**
* ✅ 备忘录写入
*/
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ... {
/**
* ✅ 写备忘录
*/
public final void writeObject(Object obj) throws IOException {
// 1️⃣ 检查是否可序列化
if (obj instanceof Serializable) {
// 2️⃣ 写入类描述
writeObject0(obj, false);
} else {
throw new NotSerializableException(obj.getClass().getName());
}
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
// 1️⃣ 处理基本类型
// 2️⃣ 处理字符串
// 3️⃣ 处理数组
// 4️⃣ 处理对象(递归写)
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| 类型检查 | Serializable |
| 递归写 | 引用对象 |
| 处理循环 | 已写对象不重写 |
3.3 ObjectInputStream:备忘录恢复
核心源码:
java
/**
* ✅ 备忘录恢复
*/
public class ObjectInputStream extends InputStream implements ObjectInput, ... {
/**
* ✅ 读备忘录
*/
public final Object readObject() throws IOException, ClassNotFoundException {
// 1️⃣ 读类描述
// 2️⃣ 创建对象
// 3️⃣ 读字段值
// 4️⃣ 恢复对象
}
}
3.4 serialVersionUID:备忘录版本
java
/**
* ✅ 备忘录版本
*/
public class Goods implements Serializable {
/**
* ✅ 序列化版本号
*/
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
关键点:
| 关键点 | 解释 |
|---|---|
serialVersionUID |
版本号 |
| 不一致 | 抛 InvalidClassException |
| 显式声明 | 推荐 |
3.5 Serializable 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 标记接口 | 无方法 | 简洁 |
| 自动序列化 | JVM 处理 | 易用 |
serialVersionUID |
版本控制 | 兼容 |
transient |
字段控制 | 灵活 |
四、数据库事务:事务回滚备忘录
4.1 事务 = 备忘录
数据库事务用备忘录思想实现回滚:
BEGIN
↓
保存当前状态(Undo Log)
↓
UPDATE / DELETE
↓
COMMIT
├── 成功 → 销毁 Undo Log
└── 失败 → 从 Undo Log 恢复
4.2 MySQL Undo Log:事务备忘录
核心机制:
sql
-- 1️⃣ 事务开始
BEGIN;
-- 2️⃣ 修改前:MySQL 自动写 Undo Log
UPDATE goods SET price = 100 WHERE id = 1;
-- Undo Log: price=200 (修改前)
-- 3️⃣ 提交
COMMIT;
-- Undo Log 被清空
-- 或
-- 4️⃣ 回滚
ROLLBACK;
-- 从 Undo Log 恢复: price=200
关键点:
| 关键点 | 解释 |
|---|---|
| Undo Log | 事务备忘录 |
| 修改前保存 | Originator |
| ROLLBACK 恢复 | restore |
| COMMIT 销毁 | 提交后销毁 |
4.3 MyBatis 一级缓存:会话备忘录
核心源码(简化):
java
/**
* ✅ MyBatis 一级缓存(会话备忘录)
*/
public class BaseExecutor implements Executor {
protected PerpetualCache localCache; // ✅ 备忘录
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 1️⃣ 清空缓存(事务操作)
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public <E> List<E> query(MappedStatement ms, ...) {
// 2️⃣ 查缓存
List<E> list = (List<E>) localCache.getObject(key);
if (list != null) {
return list;
}
// 3️⃣ 查 DB 并缓存
list = queryFromDatabase(...);
localCache.putObject(key, list);
return list;
}
}
关键点:
| 关键点 | 解释 |
|---|---|
localCache |
备忘录 |
clearLocalCache |
修改后清空 |
getObject |
查缓存 |
4.4 数据库事务的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| Undo Log | 数据库内置 | 性能 |
| 自动写 | 修改前自动 | 透明 |
| COMMIT 清空 | 提交后销毁 | 一致性 |
| ROLLBACK 恢复 | 从 Undo Log 恢复 | 数据正确 |
五、为什么框架作者这么设计?
读源码看的是"实现",悟的是"设计哲学"。
5.1 框架作者选备忘录的 4 个核心考虑
| 序号 | 考虑 | 举例 |
|---|---|---|
| ① | 状态保存 | 状态机 / 事务 |
| ② | 数据持久化 | 序列化 |
| ③ | 回滚恢复 | 事务 / 撤销 |
| ④ | 多版本管理 | 状态机 |
5.2 备忘录 vs 序列化:源码里怎么选?
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 内存状态 | 备忘录 | 快 |
| 持久化 | 序列化 | 跨进程 |
| 事务回滚 | 数据库 Undo Log | 内置 |
| 状态机 | 备忘录 | 持久化 |
5.3 框架作者不选备忘录的 3 种情况
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 业务简单 | 没必要 |
| ② | 性能关键 | 备忘录开销 |
| ③ | 单次操作 | 不用保存 |
5.4 备忘录在企业级应用的 3 种典型用法
用法 1:状态机持久化
Spring StateMachinePersist
→ 跨进程状态恢复
用法 2:购物车快照
自定义 Memento
→ 用户撤销/恢复
用法 3:事务回滚
数据库 Undo Log
→ ACID 保证
六、借鉴到我们项目:3 个"抄作业"实践
6.1 实践 1:购物车快照
场景:大白商城购物车多版本。
完整实现:
java
/**
* 借鉴 StateMachinePersist:购物车快照持久化
*/
@Service
public class CartSnapshotService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* ✅ 保存快照
*/
public void saveSnapshot(String userId, CartMemento memento) {
String key = "cart:snapshot:" + userId + ":" + memento.getSnapshotId();
redisTemplate.opsForValue().set(key, JSON.toJSONString(memento));
// ✅ 同时维护版本列表
String listKey = "cart:versions:" + userId;
redisTemplate.opsForList().rightPush(listKey, memento.getSnapshotId());
}
/**
* ✅ 恢复快照
*/
public CartMemento loadSnapshot(String userId, String snapshotId) {
String key = "cart:snapshot:" + userId + ":" + snapshotId;
String json = redisTemplate.opsForValue().get(key);
return JSON.parseObject(json, CartMemento.class);
}
/**
* ✅ 获取所有版本
*/
public List<String> getVersions(String userId) {
return redisTemplate.opsForList().range("cart:versions:" + userId, 0, -1);
}
}
6.2 实践 2:订单状态机持久化
场景:大白商城订单状态机(重启后恢复)。
完整实现:
java
/**
* 借鉴 StateMachinePersist:订单状态机持久化
*/
public class OrderStateMachinePersist implements StateMachinePersist<String, String, String> {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void write(StateMachineContext<String, String> context, String orderId) {
String state = context.getState().toString();
redisTemplate.opsForValue().set("order:state:" + orderId, state);
}
@Override
public StateMachineContext<String, String> read(String orderId) {
String state = redisTemplate.opsForValue().get("order:state:" + orderId);
return new DefaultStateMachineContext<>(state);
}
}
// 使用
public void persistOrderStateMachine(String orderId) {
stateMachine.stop();
stateMachinePersist.write(stateMachine, orderId);
}
public void restoreOrderStateMachine(String orderId) {
StateMachineContext<String, String> context = stateMachinePersist.read(orderId);
stateMachine.stop();
// 恢复状态机
// ...
stateMachine.start();
}
6.3 实践 3:业务操作回滚
场景:大白商城批量操作(部分失败回滚)。
完整实现:
java
/**
* 借鉴 Undo Log:业务操作回滚
*/
public class OperationRollbackManager {
/**
* ✅ 执行带回滚的操作
*/
public void executeWithRollback(List<RollbackableOperation> operations) {
// 1️⃣ 备忘录栈:保存每步操作前的状态
Deque<Runnable> rollbacks = new ArrayDeque<>();
try {
for (RollbackableOperation op : operations) {
// 2️⃣ 注册回滚
rollbacks.push(op.getRollback());
// 3️⃣ 执行
op.execute();
}
} catch (Exception e) {
// 4️⃣ 失败:依次回滚
log.error("操作失败,开始回滚", e);
while (!rollbacks.isEmpty()) {
try {
rollbacks.pop().run();
} catch (Exception ex) {
log.error("回滚失败", ex);
}
}
throw e;
}
}
}
@FunctionalInterface
public interface RollbackableOperation {
void execute();
default Runnable getRollback() {
return () -> {};
}
}
收益:
| 优化点 | 效果 |
|---|---|
| 购物车快照 | 多版本可恢复 |
| 状态机持久化 | 跨重启状态 |
| 操作回滚 | 批量操作可回退 |
七、本篇小结 + 下一模式预告
7.1 本篇小结(5 个核心要点)
- Spring StateMachinePersist :
write/read接口 + Redis 持久化。 - JDK Serializable :标记接口 + JVM 自动序列化。
- 数据库事务 Undo Log :修改前快照 + 回滚恢复。
- 设计哲学 :快照 + 恢复 + 事务一致性。
- 借鉴价值 :购物车快照 / 状态机持久化 / 业务回滚 ,任何"状态保存"都用备忘录。
7.2 一句话总结
备忘录在源码里不是"保存状态"的形象化,而是"快照 + 恢复 + 事务"三件套的工业级架构 。StateMachinePersist 持久化状态,Serializable 序列化对象,数据库 Undo Log 实现事务回滚。学备忘录,先学 StateMachinePersist 和 Serializable。
7.3 知识脑图
备忘录源码剖析
├── Spring
│ ├── StateMachinePersist(接口)
│ │ ├── write(context, id)
│ │ └── read(id)
│ ├── DefaultStateMachineContext
│ │ ├── state
│ │ ├── events
│ │ └── variables
│ └── RedisStateMachinePersist
├── JDK
│ ├── Serializable(标记接口)
│ ├── ObjectOutputStream
│ ├── ObjectInputStream
│ └── serialVersionUID
├── 数据库
│ ├── 事务 Undo Log
│ └── MyBatis 一级缓存
└── 借鉴价值
├── 购物车快照
├── 订单状态机持久化
└── 业务操作回滚
7.4 下一模式预告
第 42 篇【访问者模式 - 业务实战篇】:大白商城营销规则计算的"数据结构与操作分离"
下一篇我们会实战访问者模式,回答 4 个问题:
- 营销规则计算(满减/折扣/立减)怎么灵活扩展?
- 访问者 vs 策略 / 中介者怎么区分?
- JDK 编译器 API 怎么用访问者?
- ASM / Spring SpEL 怎么借鉴访问者?
并附完整的营销规则访问者实战代码(含访问者 + 元素 + 对象结构 + 单元测试),可直接复制到 IDEA 跑。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯