Java实现状态机案例

wen java案例 1

Java实现状态机案例详解:从状态模式到Spring状态机实战


目录导读

  1. 状态机核心概念:什么是状态机?为什么用状态机?
  2. 状态机的三种Java实现方案:手写状态模式、枚举状态机、Spring StateMachine框架
  3. 实战案例:订单状态流转(待支付→已支付→已发货→已完成/已取消)
  4. 状态机与工作流引擎的区别:何时用轻量级状态机,何时用Activiti
  5. 高频面试题:状态机如何保证原子性?状态机与有限状态自动机(FSA)的关系?
  6. 性能与扩展性优化:事件驱动、持久化、并发控制

状态机核心概念:用“地铁闸机”秒懂

状态机(State Machine)本质是一个“规则引擎”,它由三要素组成:现态(当前状态)事件(触发动作)次态(迁移后状态),以地铁闸机为例:

Java实现状态机案例

  • 状态: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状态机中可配置OrderStateMachineStateMachinePersister,利用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()进行兜底。


性能与扩展性优化实践

  1. 事件驱动:结合Spring ApplicationEvent发布状态变更事件,解耦订单模块与消息通知模块。
  2. 持久化优化:将状态变更写入log表(如order_status_log),便于审计和异常恢复。
  3. 并发测试:用Jmeter模拟千级并发支付,压测时关注UPDATE语句的行锁竞争率。

Java状态机不仅仅是一种设计模式,更是规范复杂业务流的一剂良药,从手写状态模式到成熟的Spring StateMachine框架,核心在于将“变化”与“不变化”分离,若你的项目仍被繁杂的if层层嵌套折磨,不妨用状态机重构一次,让代码回归“有序艺术”。

抱歉,评论功能暂时关闭!