在后台系统开发中,状态流转无处不在:订单从 待支付 → 已支付 → 已发货,工单从 打开 → 处理中 → 解决。
一开始用一堆 if/else、嵌套判断能跑通,但很快会出现:
-
代码像洋葱层层嵌套(箭头代码,难以阅读)
-
状态规则散落在多个分支里,难以维护与测试
-
边界条件容易遗漏,扩展差(新增状态/事件要改核心逻辑)
所以,状态机(State Machine)诞生了——它是把状态与转移规则显式化、模块化、可测试化的解法。
状态机核心概念
状态机三要素:
|
要素 |
含义 |
例子(订单系统) |
|---|---|---|
| 状态(State) |
系统所处的稳定阶段 |
待支付 / 已支付 / 已发货 |
| 事件(Event) |
能触发状态变化的动作 |
支付成功 / 取消订单 / 发货 |
| 转移(Transition) |
从某个状态在某个事件下到另一个状态并执行处理 |
待支付 + 支付成功 → 已支付(并执行支付逻辑) |
状态机的常见类型:
-
有限状态机(FSM):最常用、最简单,适合大多数业务场景。
-
分层状态机(HSM):支持状态继承,减少重复(复杂场景)。
-
状态图 / Statecharts:支持并发子状态、历史状态等高级特性(用于复杂流程)。
状态机实战案例
一个清晰且可扩展的状态机实现
// TransitionHandler 是一个函数式接口,可以用 lambda 实现
@FunctionalInterface
public interface TransitionHandler {
void handle() throws Exception;
}
// 状态与事件用 enum 表示
public enum State {
PENDING, PAID, SHIPPED, CANCELED
}
public enum Event {
PAY_SUCCESS, CANCEL, SHIP
}
// 转移定义
public class Transition {
public final State from;
public final Event event;
public final State to;
public final TransitionHandler handler;
public Transition(State from, Event event, State to, TransitionHandler handler) {
this.from = from;
this.event = event;
this.to = to;
this.handler = handler;
}
}
// 简单状态机实现(遍历转移表)
import java.util.ArrayList;
import java.util.List;
public class StateMachine {
private State current;
private final List<Transition> transitions = new ArrayList<>();
public StateMachine(State initial) {
this.current = initial;
}
public void addTransition(State from, Event event, State to, TransitionHandler handler) {
transitions.add(new Transition(from, event, to, handler));
}
public void trigger(Event event) throws Exception {
for (Transition t : transitions) {
if (t.from == current && t.event == event) {
// 执行处理
t.handler.handle();
// 更新状态
current = t.to;
return;
}
}
throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持");
}
public State getCurrent() {
return current;
}
}
使用示例(main):
public static void main(String[] args) throws Exception {
StateMachine sm = new StateMachine(State.PENDING);
sm.addTransition(State.PENDING, Event.PAY_SUCCESS, State.PAID, () -> {
System.out.println("执行支付成功处理逻辑...");
});
sm.addTransition(State.PENDING, Event.CANCEL, State.CANCELED, () -> {
System.out.println("执行订单取消逻辑...");
});
sm.addTransition(State.PAID, Event.SHIP, State.SHIPPED, () -> {
System.out.println("执行发货逻辑...");
});
System.out.println("当前状态: " + sm.getCurrent());
sm.trigger(Event.PAY_SUCCESS);
System.out.println("当前状态: " + sm.getCurrent());
sm.trigger(Event.SHIP);
System.out.println("当前状态: " + sm.getCurrent());
}
输出:
当前状态: 待支付
执行支付成功处理逻辑...
当前状态: 已支付
执行发货逻辑...
当前状态: 已发货
优化:表驱动实现(查找 O(1))
原实现触发时遍历转移表,性能在大量状态/事件时受影响。把转移表做成 map[State]map[Event]*Transition,触发变成两次 map 查找,既直观又高效:
import java.util.HashMap;
import java.util.Map;
public class StateMachineV2 {
private State current;
private Map<State, Map<Event, Transition>> transitionMap;
public StateMachineV2(State initial) {
this.current = initial;
this.transitionMap = new HashMap<>();
}
public void addTransition(State from, Event event, State to, TransitionHandler handler) {
transitionMap
.computeIfAbsent(from, k -> new HashMap<>())
.put(event, new Transition(from, event, to, handler));
}
public void trigger(Event event) throws Exception {
Map<Event, Transition> events = transitionMap.get(current);
if (events != null) {
Transition t = events.get(event);
if (t != null) {
t.handler.handle();
current = t.to;
return;
}
}
throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持");
}
public State getCurrent() {
return current;
}
}
面向对象风格:状态模式
用接口把每个状态封装成一个对象,状态对象实现该状态下能做的操作。好处是逻辑分散到各自状态类,遵循单一职责;坏处是类型和样板代码增多。
public interface OrderState {
void pay(OrderContext ctx) throws Exception;
void cancel(OrderContext ctx) throws Exception;
void ship(OrderContext ctx) throws Exception;
}
public class PendingState implements OrderState {
@Override
public void pay(OrderContext ctx) {
System.out.println("Pending -> 支付处理");
ctx.setState(new PaidState());
}
@Override
public void cancel(OrderContext ctx) {
System.out.println("Pending -> 取消订单");
ctx.setState(new CanceledState());
}
@Override
public void ship(OrderContext ctx) {
throw new IllegalStateException("当前状态不能发货");
}
}
public class PaidState implements OrderState {
@Override
public void pay(OrderContext ctx) {
throw new IllegalStateException("已支付,无法重复支付");
}
@Override
public void cancel(OrderContext ctx) {
System.out.println("Paid -> 已支付取消逻辑");
ctx.setState(new CanceledState());
}
@Override
public void ship(OrderContext ctx) {
System.out.println("Paid -> 发货处理");
ctx.setState(new ShippedState());
}
}
// 其余状态类略...
public class OrderContext {
private OrderState state;
public OrderContext(OrderState state) { this.state = state; }
public void setState(OrderState state) { this.state = state; }
public String getStateName() { return state.getClass().getSimpleName(); }
public void pay() throws Exception { state.pay(this); }
public void cancel() throws Exception { state.cancel(this); }
public void ship() throws Exception { state.ship(this); }
}
并发与安全:
在并发场景下,必须保证状态转移是原子且幂等的:
-
简单做法:在
Trigger外包一层mutex.Lock()/Unlock()。 -
更复杂:使用 DB 乐观锁(version)或分布式锁(Redis、Zookeeper)保证跨进程一致性。
-
异步场景:用消息队列序列化事件处理,保证顺序性。
public class SafeStateMachineV2 extends StateMachineV2 {
private final Object lock = new Object();
public SafeStateMachineV2(State initial) { super(initial); }
@Override
public void trigger(Event event) throws Exception {
synchronized (lock) {
super.trigger(event);
}
}
}
持久化:
数据库里只记 order.status、事件历史(audit trail)与时间戳。代码在恢复时:从 DB 读回当前状态并注入状态机即可重建运行时行为(状态机逻辑不存入 DB)。
// 存储示例(伪代码)
orders(
id BIGINT PRIMARY KEY,
status VARCHAR(32),
event_history JSON,
version BIGINT,
updated_at TIMESTAMP
)
// 恢复:
Order order = loadOrderFromDB(id);
StateMachineV2 sm = CreatePreconfiguredStateMachine(order.getStatusEnum());
持久化事务注意:
-
在触发转移时,应先执行业务逻辑(可能跨系统),再持久化状态与审计(尽量保证幂等)。
-
使用事务 + 幂等设计(或事务外补偿)保证一致性。
反模式与实践建议
别犯这些坑:
-
过度复杂:状态超过 ~15 个,考虑拆分多个子状态机。
-
上帝状态机:一个状态机控制太多业务,职责混乱。
-
忽略回退:关键流程必须设计补偿/回退逻辑(尤其是跨系统流程)。
-
缺乏监控:必须记录状态转移日志与失败告警。
监控示例(埋点):
在 trigger 前后记录日志、耗时、失败信息,监控异常率、非法事件请求。
public void triggerWithMonitoring(Event event) throws Exception {
long start = System.currentTimeMillis();
State old = getCurrent();
try {
trigger(event);
} finally {
System.out.printf("state transition: %s -> %s on %s, took %d ms%n",
old, getCurrent(), event, System.currentTimeMillis() - start);
}
}
总结
通过本文,你应该能做到:
-
理解状态 / 事件 / 转移三要素
-
会写一个简单可扩展的 Java 状态机
-
知道如何做性能优化(表驱动)与并发保护
-
理解持久化原则:只存状态,记录历史
-
避免常见反模式,做好监控与可恢复设计
状态机不仅是代码结构,更是工程思维
1057

被折叠的 条评论
为什么被折叠?



