Solon Framework

核心思想

状态机有四个构建块:

概念 含义
状态 系统所处的条件(例如,“CREATED”、“PAID”)
事件 发生的事情(例如,“PAY”、“SHIP”)
动作 转换发生时运行的内容
转换 规则:从状态 X,在事件 Y 发生时,转换到状态 Z(可选条件 W,并执行动作 A)

Solon 的 StateMachine<State, Event, Payload> 通过流畅的 DSL 将它们整合在一起。

Hello World:一个灯开关

添加依赖:

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-statemachine</artifactId>
</dependency>

进入全屏模式 退出全屏模式

现在,一个在 OFF 和 ON 之间切换的灯开关:

import org.noear.solon.statemachine.EventContext;
import org.noear.solon.statemachine.StateMachine;

public class LightSwitchApp {
    enum Event { PRESS }
    enum State { OFF, ON }

    public static void main(String[] args) {
        StateMachine<State, Event, String> stateMachine = new StateMachine<>();

        // OFF + PRESS -> ON
        stateMachine.addTransition(t -> t.from(State.OFF).on(Event.PRESS).to(State.ON)
                .then(context -> System.out.println("Light is ON")));

        // ON + PRESS -> OFF
        stateMachine.addTransition(t -> t.from(State.ON).on(Event.PRESS).to(State.OFF)
                .then(context -> System.out.println("Light is OFF")));

        // 测试它
        State currentState = State.OFF;
        for (int i = 0; i < 6; i++) {
            currentState = stateMachine.sendEvent(
                Event.PRESS,
                EventContext.of(currentState, null)
            );
            System.out.println("New state: " + currentState);
        }
    }
}

进入全屏模式 退出全屏模式

运行它,灯会来回切换。sendEvent() 返回新状态 —— 你不需要手动管理状态变量;只需传入当前状态,让状态机计算下一个状态。

DSL 解析

StateTransitionDecl 有五种方法:

方法 是否必需 描述
.from(state) 源状态 —— 可以有多个
.to(state) 目标状态(一个)
.on(event) 触发事件
.when(condition) 可选的守卫条件
.then(action) 可选的执行动作

whenthen 是可选的。如果你只需要一个简单的状态转换而不带副作用:

stateMachine.addTransition(t ->
    t.from(State.DRAFT).to(State.PUBLISHED).on(Event.PUBLISH));

进入全屏模式 退出全屏模式

真实世界示例:订单状态机

让我们用状态 NONE -> CREATED -> PAID -> SHIPPED -> DELIVERED 建模一个订单生命周期,带有可选的取消路径。

public enum OrderEvent { CREATE, PAY, SHIP, DELIVER, CANCEL }
public enum OrderState { NONE, CREATED, PAID, SHIPPED, DELIVERED, CANCELLED }

进入全屏模式 退出全屏模式

定义一个实现 EventContext 的 Order 负载:

public class Order implements EventContext<OrderState, Order> {
    private String id;
    private String product;
    private OrderState state;
    private String status;

    @Override
    public OrderState getState() { return state; }
    @Override
    public Order getPayload() { return this; }

    public void setState(OrderState state) { this.state = state; }
    public void setStatus(String status) { this.status = status; }
}

进入全屏模式 退出全屏模式

现在通过继承 StateMachine 构建状态机:

public class OrderStateMachine extends StateMachine<OrderState, OrderEvent, Order> {
    public OrderStateMachine() {
        from(OrderState.NONE).on(OrderEvent.CREATE).to(OrderState.CREATED).then(ctx -> {
            Order payload = ctx.getPayload();
            payload.setState(OrderState.CREATED);
            payload.setStatus("Created");
        });
        from(OrderState.CREATED).on(OrderEvent.PAY).to(OrderState.PAID).then(ctx -> {
            Order payload = ctx.getPayload();
            payload.setState(OrderState.PAID);
            payload.setStatus("Paid");
        });
        from(OrderState.PAID).on(OrderEvent.SHIP).to(OrderState.SHIPPED).then(ctx -> {
            Order payload = ctx.getPayload();
            payload.setState(OrderState.SHIPPED);
            payload.setStatus("Shipped");
        });
        from(OrderState.SHIPPED).on(OrderEvent.DELIVER).to(OrderState.DELIVERED).then(ctx -> {
            Order payload = ctx.getPayload();
            payload.setState(OrderState.DELIVERED);
            payload.setStatus("Delivered");
        });
    }
}

进入全屏模式 退出全屏模式

测试它:

public class OrderTest {
    public static void main(String[] args) {
        OrderStateMachine stateMachine = new OrderStateMachine();
        Order order = new Order("1", "iPhone 16 Pro Max", null);

        stateMachine.sendEvent(OrderEvent.CREATE, order);
        stateMachine.sendEvent(OrderEvent.PAY, order);
        stateMachine.sendEvent(OrderEvent.SHIP, EventContext.of(order.getState(), order));
        stateMachine.sendEvent(OrderEvent.DELIVER, EventContext.of(order.getState(), order));
    }
}

进入全屏模式 退出全屏模式

两种使用风格

1. Lambda 风格(组合)

StateMachine<State, Event, Payload> sm = new StateMachine<>();
sm.addTransition(t -> t.from(X).on(E).to(Y).then(ctx -> { ... }));

进入全屏模式 退出全屏模式

2. 继承风格

public class MyMachine extends StateMachine<State, Event, Payload> {
    public MyMachine() {
        from(X).on(E).to(Y).then(ctx -> { ... });
    }
}

进入全屏模式 退出全屏模式

两者产生相同的结果。当你需要多个独立的机器时使用组合;当机器是一个自包含的领域概念时使用继承。

何时使用(以及何时不使用)

Solon 状态机被设计为 Solon Flow 的补充,而不是替代品。

在以下情况使用状态机:

  • 你有离散的状态和事件(订单生命周期、文档工作流、游戏状态)
  • 你想要声明式的替代方案来替代嵌套的 if-else 或 switch 语句
  • 你不需要持久化 —— 状态通过事件上下文在内存中维护

在以下情况使用 Solon Flow:

  • 你需要持久的、可恢复的长时间运行的流程
  • 你需要为业务用户提供可视化设计器
  • 你需要复杂的分支、并行执行或子流程

两者可以在同一个项目中共存。

真实情况

无持久化。 状态机在设计上是无状态的 —— 你通过 EventContext 传入当前状态,机器返回新状态。你负责存储。

无可视化设计器。 与 Solon Flow 拥有基于 Web 的设计器不同,状态机仅通过代码实现。

无内置事件总线集成。 状态转换同步触发 .then() 动作。

简单但对复杂工作流不够强大。 如果你的状态逻辑涉及并行状态或嵌套状态机,Solon Flow 是更好的选择。

总结

方面 详情
依赖 org.noear:solon-statemachine(自 v3.4.3 起)
核心类 StateMachine<State, Event, Payload>
DSL .from().on().to().when().then()
上下文 EventContext.of(state, payload) 或自定义实现
持久化 无(调用者拥有,通过上下文传递)
最佳用途 订单生命周期、文档工作流、游戏状态、切换

本文所有示例均来自 solon.noear.org 的官方文档。