Java实现状态机案例详解:从状态模式到Spring状态机实战
目录导读
- 状态机核心概念:什么是状态机?为什么用状态机?
- 状态机的三种Java实现方案:手写状态模式、枚举状态机、Spring StateMachine框架
- 实战案例:订单状态流转(待支付→已支付→已发货→已完成/已取消)
- 状态机与工作流引擎的区别:何时用轻量级状态机,何时用Activiti
- 高频面试题:状态机如何保证原子性?状态机与有限状态自动机(FSA)的关系?
- 性能与扩展性优化:事件驱动、持久化、并发控制
状态机核心概念:用“地铁闸机”秒懂
状态机(State Machine)本质是一个“规则引擎”,它由三要素组成:现态(当前状态)、事件(触发动作)、次态(迁移后状态),以地铁闸机为例:

- 状态:
LOCKED(锁定)、UNLOCKED(解锁) - 事件:投币(
COIN)、刷卡通过(PASS) - 规则:锁定+投币→解锁;解锁+通过→锁定
为什么用状态机? 在复杂业务(订单、审批、游戏角色)中,混乱的if/else嵌套会导致代码腐化,状态机将“状态迁移”显式建模,使逻辑可预测、可测试、可可视化。
状态机的三种主流Java实现方案
方案1:手写状态模式(GoF经典)
定义状态接口,每个状态一个类,以订单为例:
public interface OrderState {
void pay(OrderContext ctx);
void ship(OrderContext ctx);
}
public class PaidState implements OrderState {
public void pay(OrderContext ctx) { /* 抛异常:已支付 */ }
public void ship(OrderContext ctx) { ctx.setState(new ShippedState()); }
}
优点:面向对象清晰;缺点:类爆炸(N个状态×M个事件)。
方案2:枚举状态机(推荐轻量场景)
用Enum + Map存储迁移表:
public enum OrderStatus {
WAIT_PAY {
public OrderStatus handle(Event e) {
return e == Event.PAY ? WAIT_SHIP : this;
}
},
WAIT_SHIP { ... };
abstract OrderStatus handle(Event e);
}
优势:代码量减少60%,且天然支持单例,适用于状态少于10个、无复杂异步的场景。
方案3:Spring StateMachine框架(企业级)
- 使用
StateMachineBuilder或@Configuration配置状态与转移。 - 内置持久化、监听器(StateMachineListener)、动作(Action)与守卫(Guard)。
@Configuration public class OrderStateMachineConfig { @Bean public StateMachine<OrderStatus, OrderEvent> build() { StateMachineBuilder.Builder<OrderStatus, OrderEvent> builder = StateMachineBuilder.builder(); builder.configureStates().withStates() .initial(WAIT_PAY).states(EnumSet.allOf(OrderStatus.class)); builder.configureTransitions() .withExternal().source(WAIT_PAY).target(WAIT_SHIP) .event(PAY).action(payAction()); return builder.build(); } }核心价值:用DSL替代代码,支持分布式锁、幂等校验,适合微服务架构。
实战案例:订单状态流转全解析
场景:电商订单,状态包括待支付、已支付、已发货、已完成、已取消。
事件:PAY(支付)、SHIP(发货)、RECEIVE(确认收货)、CANCEL(取消)。
规则约束:
- 待支付 →(PAY)→ 已支付 | (CANCEL)→ 已取消
- 已支付 →(SHIP)→ 已发货
- 已发货 →(RECEIVE)→ 已完成
- 已支付/已发货 →(CANCEL)→ 已取消(需退款)
关键实现细节(含问答):
Q1:如何防止重复支付(并发事件)?
A:在handle()方法中加synchronized锁或使用数据库UPDATE ... WHERE status = '待支付'行级锁,Spring状态机中可配置OrderStateMachine的StateMachinePersister,利用Redis做原子状态比较。
Q2:状态机中如何执行“副作用”(如发短信、更新库存)?
A:在动作(Action)中完成,枚举方案中,在handle()内执行sendSms();Spring状态机中通过action()回调注入CommandLineRunner。
Q3:如何持久化状态?
A:在DB中存status字段即可,每次状态变更用UPDATE t_order SET status = ? WHERE id = ? AND status = ?,影响行数=1则成功,否则回滚。
状态机 vs 工作流引擎
- 状态机:适合状态少、流转确定(订单、支付),代码轻量,无外部依赖。
- 工作流(Activiti):适合人工审批、并行多分支、会签(OA系统),需部署流程文件(BPMN)。
选择建议:低于6个状态且无并行需求,直接用枚举状态机;若后续要可视化编辑,则用Spring StateMachine。
高频面试题深度解析
Q4:状态机如何保证操作的原子性?
A:在数据库层使用乐观锁(version字段)或条件更新(UPDATE WHERE status = ?),若涉及跨服务,则用分布式锁(Redis/ZooKeeper)包裹状态迁移方法。
Q5:状态机与有限状态自动机(FSA)有何区别?
A:Java状态机实现是FSA的工程落地,FSA是数学概念(五元组),而Java实现额外关注行为封装、持久化、事务。
Q6:状态机中如何优雅处理“非法事件”?
A:定义invalidTransition处理函数,枚举方案中可抛IllegalStateException;Spring状态机中监听StateMachineEvent并调用stateMachine.getState().getId()进行兜底。
性能与扩展性优化实践
- 事件驱动:结合
Spring ApplicationEvent发布状态变更事件,解耦订单模块与消息通知模块。 - 持久化优化:将状态变更写入
log表(如order_status_log),便于审计和异常恢复。 - 并发测试:用
Jmeter模拟千级并发支付,压测时关注UPDATE语句的行锁竞争率。
Java状态机不仅仅是一种设计模式,更是规范复杂业务流的一剂良药,从手写状态模式到成熟的Spring StateMachine框架,核心在于将“变化”与“不变化”分离,若你的项目仍被繁杂的if层层嵌套折磨,不妨用状态机重构一次,让代码回归“有序艺术”。