easy-fsm 使用文档
easy-fsm 是一个零 Spring 依赖的纯 Java 状态机组件。基于 COLA 4.3 StateMachine 做二次封装,业务只需定义实现
NamedEntry的 State/Event 枚举,一行 Builder 链式 API 即可构建状态机,运行时支持 fireEvent / readyEvents / PlantUML 生成。
- 1. 引入依赖
- 2. 定义 State / Event 枚举
- 3. 构建状态机
- 4. 执行业务:fireEvent
- 5. 查询可执行转换:readyEvents
- 6. 条件谓词
- 7. 动作回调 FsmAction
- 8. 元数据挂载
- 9. PlantUML 可视化
- 10. 注意事项
- 11. 常见问题
1. 引入依赖
<dependency>
<groupId>io.matrix</groupId>
<artifactId>easy-fsm</artifactId>
<version>${revision}</version>
</dependency>
easy-fsm 零 Spring 依赖,可在任何 Java 项目中使用(包括非 Spring Boot 的单体应用、工具链、单元测试)。
2. 定义 State / Event 枚举
State 和 Event 枚举必须同时满足:
extends Enum<E>(枚举)implements NamedEntry(kernel-client 提供)
public enum OrderState implements NamedEntry {
CREATED("CREATED", "已创建"),
PAID("PAID", "已支付"),
SHIPPED("SHIPPED", "已发货"),
COMPLETED("COMPLETED", "已完成"),
CANCELLED("CANCELLED", "已取消");
private final String code;
private final String name;
OrderState(String code, String name) { this.code = code; this.name = name; }
@Override public String getCode() { return code; }
@Override public String getName() { return name; }
}
public enum OrderEvent implements NamedEntry {
PAY("PAY", "支付"),
SHIP("SHIP", "发货"),
CANCEL("CANCEL", "取消"),
COMPLETE("COMPLETE", "完成");
private final String code;
private final String name;
OrderEvent(String code, String name) { this.code = code; this.name = name; }
@Override public String getCode() { return code; }
@Override public String getName() { return name; }
}
// 业务上下文,携带条件判断所需的运行时数据
public class OrderCtx {
private int amount;
public int getAmount() { return amount; }
public OrderCtx amount(int a) { this.amount = a; return this; }
}
约束:
Fsm<S extends Enum<S> & NamedEntry, E extends Enum<E> & NamedEntry, C>泛型三参数缺一不可。没有实现 NamedEntry 会在编译期报错。
3. 构建状态机
生产代码通常在 @PostConstruct 中构建一次,之后复用 Fsm 实例:
// Builder 第一个参数是唯一 machineId(COLA StateMachineFactory 全局 Map 的 key)
Fsm<OrderState, OrderEvent, OrderCtx> fsm = FsmBuilder
.builder(OrderState.class, OrderEvent.class, OrderCtx.class, "order-machine")
// 外部转换:from → to,事件
.external(OrderState.CREATED, OrderState.PAID, OrderEvent.PAY)
.external(OrderState.PAID, OrderState.SHIPPED, OrderEvent.SHIP)
.external(OrderState.SHIPPED, OrderState.COMPLETED, OrderEvent.COMPLETE)
.external(OrderState.CREATED, OrderState.CANCELLED, OrderEvent.CANCEL)
.external(OrderState.PAID, OrderState.CANCELLED, OrderEvent.CANCEL)
.build();
COLA StateMachineFactory 单例限制:
build(name)的 name 在 COLA 全局 Map 中唯一。重复 build 相同 name 会抛StateMachineException: The state machine with id [xxx] is already built. 每个 Builder 实例只能调一次build()。
4. 执行业务:fireEvent
OrderCtx ctx = new OrderCtx();
OrderState next = fsm.fireEvent(order.getState(), OrderEvent.PAY, ctx);
order.setState(next); // next == OrderState.PAID
fireEvent 对无匹配转换的行为:返回 sourceState(COLA StateMachine 默认策略)。如需显式检查:
if (!fsm.readyEvent(order.getState(), OrderEvent.SHIP, ctx)) {
throw new IllegalStateException("当前状态不可发货");
}
5. 查询可执行转换:readyEvents
查询某状态当前可执行的所有事件(带 ctx 会计算条件谓词,不带 ctx 条件视为满足):
// 不带 ctx:返回 PAID 状态下所有声明的转换
Transits<State, Event> all = fsm.readyEvents(OrderState.PAID);
// 带 ctx:只返回条件满足的转换(金额 ≥ 10000 才会包含 AUTO_SHIP)
Transits<State, Event> ready = fsm.readyEvents(OrderState.PAID, new OrderCtx().amount(50000));
// 判断某个事件是否在可执行列表里
boolean canShip = ready.isReady4Event(OrderEvent.SHIP);
// 获取所有可执行的目标状态
List<State> toStates = ready.getToStates();
6. 条件谓词
在 external()/internal() 调用中传入 Predicate<C> 作为条件:
.external(OrderState.PAID, OrderState.SHIPPED, OrderEvent.AUTO_SHIP,
ctx -> ctx.getAmount() >= 10000) // 只接受大订单
条件为 null 时视为恒真(满足)。
7. 动作回调 FsmAction
转换成功后自动触发回调,用于审计日志、发事件通知等:
import io.matrix.fsm.FsmAction;
.external(OrderState.CREATED, OrderState.PAID, OrderEvent.PAY,
null, null, (from, to, evt, ctx) -> {
auditLog(from, to, evt, ctx); // 业务方法
})
FsmAction 签名:void execute(S from, S to, E event, C context)。异常会原样传播,框架不吞。
8. 元数据挂载
FsmBuilder 提供 stateAttachment / eventAttachment 把业务元数据挂到状态/事件上。ReadyEventVisitor 产出的 Transit 会携带 metadata。
9. PlantUML 可视化
一行生成状态图源码:
String puml = fsm.generatePlantUML();
// @startuml
// CREATED --> PAID : PAY
// PAID --> SHIPPED : SHIP
// ...
// @enduml
把字符串贴到 PlantUML Online Server 即可渲染成图片,挂到 wiki / 评审文档。
10. 注意事项
| 项 | 说明 |
|---|---|
| 枚举必须实现 NamedEntry | 编译期泛型约束保证 |
| 每个 Builder 实例只能 build 一次 | 重复 build 会抛 StateMachineException |
| COLA 全局 Map 不清理 | 生产单例模式;测试需唯一 id |
| fireEvent 对无匹配转换返回 sourceState | 用 readyEvent 显式检查 |
| 内部转换 internal() 状态不变 | 用于自环,如”已支付状态再次通知支付” |
toState() / toEvent() 是 protected | 子类可覆盖做自定义 entity 包装 |
11. 常见问题
Q: fireEvent 返回的 state 怎么转回业务枚举? A: 直接赋值——Fsm<S, E, C> 泛型约束保证返回值是 S(你的业务枚举类型),COLA 内部的 entity.State 被自动转回。
Q: 测试里怎么避免 StateMachineException? A: 每个 @BeforeEach 用唯一的 machineId,例如:
String id = "order-" + testInfo.getTestMethod().get().getName() + "-" + UUID.randomUUID();
FsmBuilder.builder(..., id).build();
Q: 可以动态添加转换吗? A: 不行。Fsm 是构建时静态声明,构建后不可变。如需动态路由,用条件谓词 Predicate<C> 或两个独立 Fsm 实例按场景切换。
Q: 线程安全吗? A: Fsm 实例只读,内部 StateMachine 是 final,多线程 fireEvent 安全。
Q: 雪花算法的 allowClockBackward 配置不生效? A: 这是 easy-fsm 的已知设计(硬编码 5ms 阈值),生产代码的 clock backward 逻辑不读 maxClockBackwardMs 字段。详见 design.md §九。