Java状态机案例Demo
约 3742 字大约 12 分钟
2026-04-09
「状态机 = 规则」 + 「Action = 业务」 + 「Service = 编排」
限制状态的更新,制定好规则,例:只能从状态A变更为状态B和状态C,只能从状态B变更为状态D,如果直接从状态A变更为状态D,则报错。
状态的变更是由每一个Event事件决定的,每个事件所对应的业务逻辑会由各自独立的Action去进行实现。
流程编排:连接「状态机规则」与「事件业务实现」的枢纽,用于管理事件Event与业务Action的关联关系。
private final Map<OrderEvent, OrderEventAction> actions;用途:快速回忆“订单状态机 + 业务动作编排”的设计思路,写真实代码时可直接套用。
一、业务场景与目标
- 业务场景:订单从「创建 → 支付 → 发货 → 完成 / 取消」的生命周期。
- 目标:
- 把“状态是否合法、能不能迁移”从业务代码里抽出来,放到一个纯规则的状态机中。
- 每个事件(支付、发货、完成、取消)的业务逻辑(扣款、库存、物流、积分、退款等)由独立的 Action 实现。
- 通过一个编排层(
OrderProcessService)把“状态规则”和“业务副作用”解耦。
二、整体结构总览
1. 核心类一览
- 状态 & 事件:
OrderStatus:订单状态枚举(CREATED/PAID/SHIPPED/FINISHED/CANCELED)。OrderEvent:事件枚举(PAY/SHIP/FINISH/CANCEL)。
- 状态机与上下文:
OrderStateMachine:只维护「当前状态 + 事件 → 下一状态」的二维表。OrderContext:订单上下文,包含orderId、当前status以及一些演示用的业务字段(流水号、运单号、积分)。
- 事件业务动作:
- 接口:
OrderEventAction(函数式接口)。 - 实现:
PayOrderAction:模拟支付成功、生成支付流水号。ShipOrderAction:模拟发货、生成运单号。FinishOrderAction:模拟订单完成发放积分。CancelOrderAction:模拟取消订单、释放库存 / 退款说明。
- 接口:
- 编排层:
OrderProcessService:连接状态机与 Action 的“中枢”,对外暴露fire(OrderContext, OrderEvent)。
- Demo 入口:
OrderStateMachineDemo:控制台演示两种使用方式。
2. 推荐分层思路(重要)
状态机层:
OrderStateMachine- 只做 1 件事:给你答案——“当前状态 + 事件 → 合法吗?下一状态是什么?”
- 完全不关心支付 / 物流 / 库存等业务细节。
业务动作层:
OrderEventAction+ 各实现- 每个事件各自负责业务副作用。
- 例如:
PAY负责调支付、对账、预占库存;SHIP负责出库与物流;FINISH负责积分;CANCEL负责释放库存、退款。
编排层:
OrderProcessService- 对外只提供一个统一方法:
fire(order, event)。 - 内部执行顺序:
- 用状态机
transit校验并计算 target 状态; - 找到该事件对应的
OrderEventAction,执行业务动作; - 业务成功后,将
OrderContext的状态更新为 target 状态。
- 用状态机
- 对外只提供一个统一方法:
三、各类职责与关键代码
1. 订单状态:OrderStatus
- 描述生命周期:已创建 → 已支付 → 已发货 → 已完成 / 已取消。
- 在你的代码里已经有详细 Javadoc,主要是配合状态机使用。
/**
* 演示用订单生命周期状态(与 {@link OrderStateMachine} 中允许的迁移一致)。
* <p>
* 典型路径:已创建 → 已支付 → 已发货 → 已完成;在「已创建 / 已支付」阶段可取消。
*
* @author yunze
* @since 2026/04/08
*/
public enum OrderStatus {
/** 订单已创建,待支付 */
CREATED,
/** 已支付,待发货 */
PAID,
/** 已发货,待确认完成 */
SHIPPED,
/** 订单正常结束 */
FINISHED,
/** 订单已取消 */
CANCELED
}2. 订单事件:OrderEvent
- 表示“触发状态变化的业务动作”,如
PAY、SHIP、FINISH、CANCEL。 - 实际系统中,这些事件可能来自:
- 用户操作(点击支付 / 确认收货)。
- 第三方回调(支付成功通知、物流签收回调)。
- 定时任务(超时未支付自动取消)。
/**
* 触发订单状态迁移的业务事件,由外部动作(支付成功、发货、确认收货、取消等)映射而来。
*
* @author yunze
* @since 2026/04/08
* @see OrderStateMachine
*/
public enum OrderEvent {
/** 支付完成 */
PAY,
/** 商家发货 */
SHIP,
/** 确认收货 / 流程完结 */
FINISH,
/** 取消订单 */
CANCEL
}3. 状态机:OrderStateMachine
核心点:用一个嵌套 EnumMap 存所有合法的迁移规则。
/**
* 轻量级订单状态机:用二维表(当前状态 → 事件 → 目标状态)描述合法迁移,不依赖 Spring。
* <p>
* 本示例未包含「已发货」后的取消等扩展路径,仅演示最小闭环与非法迁移校验。
* <p>
* <b>业务代码应写在哪里:</b>不要写在本类中。每个事件对应的支付、库存、物流等应实现
* {@link OrderEventAction},由 {@link OrderProcessService} 在合法迁移确定后统一调用。
*
* @author yunze
* @since 2026/04/08
* @see OrderEventAction
* @see OrderProcessService
*/
public class OrderStateMachine {
/**
* 状态转移表:外层 key 为当前 {@link OrderStatus},内层为「事件 → 下一状态」。
*/
private final Map<OrderStatus, Map<OrderEvent, OrderStatus>> transitions = new EnumMap<>(OrderStatus.class);
/**
* 注册默认转移规则:
* <ul>
* <li>CREATED + PAY → PAID;CREATED + CANCEL → CANCELED</li>
* <li>PAID + SHIP → SHIPPED;PAID + CANCEL → CANCELED</li>
* <li>SHIPPED + FINISH → FINISHED</li>
* </ul>
*/
public OrderStateMachine() {
addTransition(OrderStatus.CREATED, OrderEvent.PAY, OrderStatus.PAID);
addTransition(OrderStatus.CREATED, OrderEvent.CANCEL, OrderStatus.CANCELED);
addTransition(OrderStatus.PAID, OrderEvent.SHIP, OrderStatus.SHIPPED);
addTransition(OrderStatus.PAID, OrderEvent.CANCEL, OrderStatus.CANCELED);
addTransition(OrderStatus.SHIPPED, OrderEvent.FINISH, OrderStatus.FINISHED);
}
/**
* 根据当前状态与事件计算下一状态。
*
* @param currentStatus 当前订单状态
* @param event 发生的事件
* @return 迁移后的目标状态
* @throws IllegalStateException 当当前状态下不允许该事件时(无定义转移)
*/
public OrderStatus transit(OrderStatus currentStatus, OrderEvent event) {
Map<OrderEvent, OrderStatus> statusMap = transitions.get(currentStatus);
if (statusMap == null || !statusMap.containsKey(event)) {
throw new IllegalStateException("不支持的状态流转: " + currentStatus + " --" + event + "--> ?");
}
return statusMap.get(event);
}
/**
* 注册一条有向边:在状态 {@code from} 上发生 {@code event} 时进入 {@code to}。
*/
private void addTransition(OrderStatus from, OrderEvent event, OrderStatus to) {
transitions.computeIfAbsent(from, key -> new EnumMap<>(OrderEvent.class)).put(event, to);
}
}transit(currentStatus, event):- 查询
transitions。 - 若找不到映射,抛出
IllegalStateException(非法状态流转)。 - 找得到就返回目标
OrderStatus。
- 查询
注意:这里没有任何支付 / 库存 / 物流的代码,只是一张规则表。
4. 订单上下文:OrderContext
模拟现实中的“订单聚合根 + 衍生字段”,方便 Demo 中展示业务变化:
orderId:订单号(long)。status:当前订单状态。payTransactionId:支付后生成的外部流水号。logisticsTrackingNo:发货后的运单号。memberPointsDelta:完结时发放的积分。
真实系统里,这个类可以对应:
- 数据库中的订单主表 + 关联字段;
- 或者一个包含订单与相关子对象的 Domain Model。
/**
* 演示用订单聚合上下文:承载当前状态及本 demo 中各事件写入的「业务结果」字段。
* <p>
* 真实项目中对应:订单实体 + 支付单、物流单、库存流水等,通常与状态一起持久化。
*/
public class OrderContext {
private final long orderId;
private OrderStatus status;
/** 支付成功后写入的第三方流水号(demo) */
private String payTransactionId;
/** 发货后写入的物流单号(demo) */
private String logisticsTrackingNo;
/** 完成后累计的积分(demo) */
private int memberPointsDelta;
public OrderContext(long orderId, OrderStatus initialStatus) {
this.orderId = orderId;
this.status = Objects.requireNonNull(initialStatus);
}
public long getOrderId() {
return orderId;
}
public OrderStatus getStatus() {
return status;
}
/** 由 {@link OrderProcessService} 在业务成功后调用,更新持久化状态 */
public void setStatus(OrderStatus status) {
this.status = Objects.requireNonNull(status);
}
public String getPayTransactionId() {
return payTransactionId;
}
public void setPayTransactionId(String payTransactionId) {
this.payTransactionId = payTransactionId;
}
public String getLogisticsTrackingNo() {
return logisticsTrackingNo;
}
public void setLogisticsTrackingNo(String logisticsTrackingNo) {
this.logisticsTrackingNo = logisticsTrackingNo;
}
public int getMemberPointsDelta() {
return memberPointsDelta;
}
public void addMemberPoints(int delta) {
this.memberPointsDelta += delta;
}
@Override
public String toString() {
return "OrderContext{"
+ "orderId=" + orderId
+ ", status=" + status
+ ", payTransactionId='" + payTransactionId + '\''
+ ", logisticsTrackingNo='" + logisticsTrackingNo + '\''
+ ", memberPointsDelta=" + memberPointsDelta
+ '}';
}
}5. 事件业务接口:OrderEventAction
- 定位:某个
OrderEvent对应的一整套业务操作。 - 签名非常简单:
/**
* 某一 {@link OrderEvent} 对应的业务副作用:支付、发货、完结、取消等应各自实现本接口。
* <p>
* <b>放置位置约定(与状态机的关系):</b>
* <ul>
* <li>{@link OrderStateMachine} 只维护「状态 × 事件 → 下一状态」,不包含业务代码。</li>
* <li>本接口的实现类放在业务层(本 demo 为 {@code com.yz.statemachine.order.action} 包),
* 由 {@link OrderProcessService} 在合法迁移确定后调用。</li>
* <li>若使用 Spring,可将各 Action 声明为 Bean,在 ProcessService 中按事件注入或从 Map 获取。</li>
* </ul>
*
* @author yunze
* @since 2026/04/09
*/
@FunctionalInterface
public interface OrderEventAction {
/**
* 执行该事件对应的业务逻辑(调支付、写库存、发物流等)。成功返回后,由编排层更新订单状态。
*
* @param order 当前订单上下文(只读/有限写入由实现约定;demo 中由 action 写入扩展字段)
* @throws RuntimeException 业务失败时应抛出,编排层不应更新状态(见 {@link OrderProcessService})
*/
void execute(OrderContext order);
}- 放置位置:
- 状态机类放
state包 /rule包; - 业务动作实现放在类似
order.action的业务包里; - 编排层(
OrderProcessService)在 service 层。
- 状态机类放
6. 四个具体事件动作
都在 com.yz.statemachine.order.action 包下,每个类一个事件:
PayOrderAction:- 生成模拟支付流水号
PAY-xxxxxx; - 输出“预占库存、写支付流水”等说明。
/** * 支付事件业务:模拟拉起支付、扣款成功、预占库存、写支付流水。 */ public class PayOrderAction implements OrderEventAction { @Override public void execute(OrderContext order) { // 真实场景:调支付网关、幂等校验、与订单金额对账等 String txnId = "PAY-" + UUID.randomUUID().toString().substring(0, 8); order.setPayTransactionId(txnId); System.out.println(" [业务-PAY] 支付成功,流水号=" + txnId + ",订单=" + order.getOrderId() + "(模拟:预占库存 SKU、写入 oms_payment 流水)"); } }- 生成模拟支付流水号
ShipOrderAction:- 生成模拟运单号
SFxxxxxxxxx; - 输出“出库扣实物库存、写物流订阅”等说明。
/** * 发货事件业务:模拟创建物流单、写运单号、通知承运商。 */ public class ShipOrderAction implements OrderEventAction { @Override public void execute(OrderContext order) { // 真实场景:WMS 出库、对接物流 API、回写运单与发货时间 String tracking = "SF" + ThreadLocalRandom.current().nextInt(100000000, 999999999); order.setLogisticsTrackingNo(tracking); System.out.println(" [业务-SHIP] 已发货,运单号=" + tracking + ",订单=" + order.getOrderId() + "(模拟:出库扣实物库存、写入物流订阅)"); } }- 生成模拟运单号
FinishOrderAction:- 给用户加 50 积分(
addMemberPoints(50)); - 输出“结算佣金、评价提醒”等说明。
/** * 确认收货/完结事件业务:模拟积分、结算分账等收尾逻辑。 */ public class FinishOrderAction implements OrderEventAction { @Override public void execute(OrderContext order) { int points = 50; order.addMemberPoints(points); System.out.println(" [业务-FINISH] 订单完结,发放积分 +" + points + ",订单=" + order.getOrderId() + "(模拟:解冻营销预算、结算佣金、触发评价提醒)"); } }- 给用户加 50 积分(
CancelOrderAction:- 打印“释放预占库存;已付则创建退款单”等说明。
/** * 取消事件业务:模拟释放库存、未支付则关闭单、已支付则走退款流程。 */ public class CancelOrderAction implements OrderEventAction { @Override public void execute(OrderContext order) { // 真实场景:按当前是否已支付分支:退款 API、释放预占库存、关单原因落库 System.out.println(" [业务-CANCEL] 取消订单,订单=" + order.getOrderId() + "(模拟:释放预占库存;若已付则创建退款单并异步回调)"); } }
在真实项目中,这些地方会调用:
- 支付网关 SDK / RPC;
- 库存/仓储系统;
- 营销与积分系统;
- 退款系统等。
7. 编排服务:OrderProcessService
关键方法 fire(OrderContext order, OrderEvent event):
- 拿当前状态:
from = order.getStatus()。 - 用状态机算出目标状态:
to = stateMachine.transit(from, event)。 - 找到对应 Action:
OrderEventAction action = actions.get(event)。 - 调用业务逻辑:
action.execute(order)。 - 业务成功后,最后更新状态:
order.setStatus(to)。
默认的 actions 映射在构造函数里直接用 EnumMap new 了四个 Action,你在 Spring 里可以改成注入。
/**
* 订单流程编排:连接「状态机规则」与「事件业务实现」的枢纽。
* <p>
* <b>推荐执行顺序(与多数电商落地一致):</b>
* <ol>
* <li>用 {@link OrderStateMachine#transit(OrderStatus, OrderEvent)} 校验并解析目标状态(只做规则,不写业务)。</li>
* <li>调用 {@link OrderEventAction#execute(OrderContext)} 完成副作用;失败则抛异常,<b>不修改</b>订单状态。</li>
* <li>业务成功后,将上下文中的状态更新为步骤 1 得到的目标状态(demo 直接改内存;生产环境应在同一事务中写库)。</li>
* </ol>
* 若需「先落库再调第三方」,可改为:乐观锁更新状态 → 调支付;失败则补偿回滚状态(需额外设计)。
*/
public class OrderProcessService {
private final OrderStateMachine stateMachine;
private final Map<OrderEvent, OrderEventAction> actions;
public OrderProcessService() {
this(new OrderStateMachine(), defaultActions());
}
public OrderProcessService(OrderStateMachine stateMachine, Map<OrderEvent, OrderEventAction> actions) {
this.stateMachine = Objects.requireNonNull(stateMachine);
this.actions = Objects.requireNonNull(actions);
}
private static Map<OrderEvent, OrderEventAction> defaultActions() {
Map<OrderEvent, OrderEventAction> map = new EnumMap<>(OrderEvent.class);
map.put(OrderEvent.PAY, new PayOrderAction());
map.put(OrderEvent.SHIP, new ShipOrderAction());
map.put(OrderEvent.FINISH, new FinishOrderAction());
map.put(OrderEvent.CANCEL, new CancelOrderAction());
return map;
}
/**
* 触发一次状态迁移并执行业务:合法则先跑业务,成功后再改状态。
*
* @param order 订单上下文
* @param event 业务事件
* @throws IllegalStateException 非法迁移(与状态机一致)
* @throws RuntimeException 业务动作失败
*/
public void fire(OrderContext order, OrderEvent event) {
OrderStatus from = order.getStatus();
OrderStatus to = stateMachine.transit(from, event);
OrderEventAction action = actions.get(event);
if (action == null) {
throw new IllegalStateException("未注册事件对应的业务实现: " + event);
}
action.execute(order);
order.setStatus(to);
}
public OrderStateMachine getStateMachine() {
return stateMachine;
}
}四、示例执行流程
在 OrderStateMachineDemo.main 中有两段 Demo,你可以随时跑来复习设计。
1. 纯状态机演示(不含业务)
- 使用
OrderStateMachine直接对OrderStatus做状态迁移。 - 流程:
- CREATED → PAY → PAID
- PAID → SHIP → SHIPPED
- SHIPPED → FINISH → FINISHED
- FINISHED 上再尝试 CANCEL → 抛
IllegalStateException。
用途:只验证规则正确性。
2. 状态机 + 业务 Action 演示(推荐形态)
- 使用
OrderProcessService和OrderContext。 - 演示 2 条路径:
- 正常链路:CREATED + PAY → PAID → SHIP → SHIPPED → FINISH → FINISHED
- 每步会输出:
- 状态变化:from → to
- 业务日志:支付流水号、运单号、积分等。
- 最后打印完整
OrderContext,方便查看状态和字段变更。
- 每步会输出:
- 取消链路:CREATED + CANCEL → CANCELED
- 打印取消业务说明。
- 正常链路:CREATED + PAY → PAID → SHIP → SHIPPED → FINISH → FINISHED
通过这两个 Demo,你可以很清晰地看到:
- 状态机负责“能 / 不能 + 去哪儿”;
- Action 负责“做什么事”;
- 编排服务统一调度两者。
五、真实项目落地建议(速记)
状态机层(纯规则)
- 用类似本 Demo 的
EnumMap或表驱动方式; - 或者使用 Spring Statemachine / Squirrel 等库时,也尽量把业务放在 Action / Listener 中,而不是写进状态机配置。
- 用类似本 Demo 的
事件粒度
- 按“订单业务语义”设计事件:创建、支付成功、发货、签收、取消、退款成功等;
- 避免过细如“更新字段 X”这种不具有业务意义的事件。
幂等与事务
- 业务 Action 内部要处理幂等(尤其是支付、退款、库存扣减)。
- 状态机迁移 + 状态写库 + 业务动作,最好在同一事务模型中设计:是“先改库再调外部 + 补偿”,还是“先调外部成功再改库”——根据实际系统定。
扩展新状态 / 新事件
- 在
OrderStatus/OrderEvent中加新枚举; - 在
OrderStateMachine构造方法中addTransition对应规则; - 新增对应的
OrderEventAction实现; - 在
OrderProcessService的actionsMap 中注册。
- 在
六、总结参考
真实项目里做“订单状态机 + 业务编排”时,可以按下面的 checklist 来对照本 Demo:
- 先画出状态图(类似
OrderStatus和OrderStateMachine的 rules)。 - 定义事件枚举(
OrderEvent)。 - 写一个纯规则
StateMachine类(不含业务)。 - 写业务动作接口 + 若干实现(参考
OrderEventAction+action包)。 - 写编排服务(参考
OrderProcessService.fire的顺序与错误处理)。 - 写一个 Demo / 单测(类似
OrderStateMachineDemo),跑一遍全链路是否符合预期。
你只要记得这张 mental map:
「状态机 = 规则」 + 「Action = 业务」 + 「Service = 编排」,再结合这个 Demo 的代码结构,就可以很快在任何项目里复用。
