大白话说Java设计模式-41-备忘录模式(源码剖析篇)

📌 PDF :AI人工智能 --- AI Agent智能体实战项目

大白话说Java设计模式-41-备忘录模式(源码剖析篇):JDK / Spring 中的备忘录实现

📌 一句话本质:备忘录在源码里不是"保存状态"的形象化,而是"快照 + 恢复 + 事务"三件套的工业级架构。

🏷️ 标签:备忘录模式 / StateMachine / Serializable / 事务 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师


目录


一、为什么读备忘录源码?一张地图先看清

备忘录模式在 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 个核心要点)

  1. Spring StateMachinePersistwrite/read 接口 + Redis 持久化
  2. JDK Serializable标记接口 + JVM 自动序列化
  3. 数据库事务 Undo Log修改前快照 + 回滚恢复
  4. 设计哲学快照 + 恢复 + 事务一致性
  5. 借鉴价值购物车快照 / 状态机持久化 / 业务回滚任何"状态保存"都用备忘录

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 个问题:

  1. 营销规则计算(满减/折扣/立减)怎么灵活扩展?
  2. 访问者 vs 策略 / 中介者怎么区分?
  3. JDK 编译器 API 怎么用访问者?
  4. ASM / Spring SpEL 怎么借鉴访问者?

并附完整的营销规则访问者实战代码(含访问者 + 元素 + 对象结构 + 单元测试),可直接复制到 IDEA 跑


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
数智启示录7 分钟前
实时数据湖 Flink CDC + Kafka +Doris 【企业级实战】之Kafka 事件缓冲层 【附核心源码】 04
java·大数据·flink·kafka·数据库开发
lv__pf9 分钟前
Sentinel【TL微服务10、11】
java·微服务·sentinel
Escalating_xu26 分钟前
【C++ string 上篇】从字符串基础到容量管理、迭代器与经典算法题
java·c++·算法
曹牧1 小时前
Java:Java 数组转 List
java·开发语言·windows
下辈子不要当码农1 小时前
Day7-Linux软件编程(进程与线程(3))
java·开发语言
yinchnag1 小时前
CRTP:奇异递归模板模式在 Go 模块系统中的实现
设计模式·go
深入云栈1 小时前
一文搞懂Netty 4.2六大核心概念:Channel/EventLoop/Selector/ByteBuf 如何关联
java·后端
hh9501 小时前
Agent Plan × DeepSeek Harness:角色 Prompt 驱动的 Agent 分工优化与协作质量实验
java·前端·人工智能·prompt·adg·agent plan·adg成都社区
MetaLite1 小时前
SpringBoot接口通用返回对象Resp设计
java·spring boot·后端