Skip to content

状态机简介

状态机(State Machine)是一种计算模型,用于描述系统在不同状态下的行为以及状态之间的转移。

状态机通常包括以下几个要素:

  • 状态(States): 描述系统可能处于的不同状态。每个状态代表系统在某个时间点的特定情况或条件。

  • 转移(Transitions): 描述系统从一个状态到另一个状态的条件和动作。当满足一定条件时,系统就会根据定义好的转移规则从一个状态转移到另一个状态。

  • 事件(Events): 触发状态转移的外部或内部事件。这些事件可以是用户的输入、系统的内部触发、或者其他外部条件的改变。

  • 动作(Actions): 在状态转移发生时执行的操作或任务。这些动作可以包括更新变量、触发其他事件、或执行一些计算。

根据状态数量优有限还是无限,可以将状态机分成有限状态机(Finite State Machine,FSM)无限状态机(Infinite State Machine),通常我们讨论的都是 FSM。

以下是一个订单状态机的状态转移截取示意;

使用优势

难以想象一个多状态转移的业务流程如果不使用状态机,编码会有多么错乱且不易维护。

以下是使用状态机的优势;

  • 清晰的模型: 状态机提供了一种直观、清晰的建模方式,使得系统的行为和状态转换能够被更容易理解和可视化。这有助于开发人员和团队共同理解系统的设计和逻辑。

  • 易于调试和测试: 由于状态机的行为是基于状态和转移规则定义的,因此在调试和测试过程中,可以更容易地定位和修复问题。开发人员可以追踪系统的状态变化,检查是否符合预期。

  • 可扩展性: 状态机是一种模块化的设计方法,可以通过添加新的状态和相应的状态转移规则来扩展系统。这使得系统更易于扩展,同时保持代码的整洁性。

  • 可读性和维护性: 由于状态机提供了一种高度抽象的描述方式,使得代码更易于阅读和理解。这种清晰性有助于降低代码的维护成本。

  • 状态机框架广泛支持: 许多编程语言和框架都提供了状态机的实现或库,使得开发人员可以更轻松地集成状态机模型到他们的应用程序中。

Java 实践:简易状态机

完整代码:https://github.com/caojiantao/fsm

基于状态机的四要素,用 Java 基于 Spring 实现一个简易状态机框架。首先定义状态机上下文,便于状态转移过程中的数据处理;

java
/**
 * 状态机上下文
 *
 * @param <P> 状态类型枚举
 * @param <Q> 事件类型枚举
 */
@Data
public class FsmContext<P extends Enum<P>, Q extends Enum<Q>> {

    /**
     * 当前状态
     */
    private P currentState;

    /**
     * 触发事件
     */
    private Q triggerEvent;
}

框架需要足够简单,Transition 定义好转移条件,和执行动作;

java
/**
 * 状态转移接口
 *
 * @param <P> 状态类型
 * @param <Q> 事件类型
 * @param <T> 状态机上下文
 */
public interface IFsmTransition<P extends Enum<P>, Q extends Enum<Q>, T extends FsmContext<P, Q>> {

    /**
     * 转移前状态
     */
    P getBeforeState();

    /**
     * 触发转移的具体事件
     */
    Q getTriggerEvent();

    /**
     * 转移后状态
     */
    P getAfterState();

    /**
     * 状态转移过程执行的动作
     */
    void doAction(T context);
}

再定义状态机实体,包含状态机初始化方法和驱动状态转移;

java
/**
 * 简易状态机
 *
 * @param <P> 状态类型
 * @param <Q> 事件类型
 * @param <T> 状态机上下文
 */
public class Fsm<P extends Enum<P>, Q extends Enum<Q>, T extends FsmContext<P, Q>> {

    /**
     * 状态转移映射
     */
    private final Map<String, IFsmTransition<P, Q, T>> transitionMap = new HashMap<>();

    /**
     * 状态机初始化
     */
    public void init(List<IFsmTransition<P, Q, T>> transitionList) {
        for (IFsmTransition<P, Q, T> transition : transitionList) {
            String key = getTransitionKey(transition.getBeforeState(), transition.getTriggerEvent());
            transitionMap.put(key, transition);
        }
    }

    /**
     * 驱动状态转移
     *
     * @param context 状态机上下文
     */
    public void fire(T context) {
        String key = getTransitionKey(context.getCurrentState(), context.getTriggerEvent());
        IFsmTransition<P, Q, T> action = transitionMap.get(key);
        FsmAssert.notNull(action, "找不到 action," + key);
        action.doAction(context);
    }

    private String getTransitionKey(P beforeState, Q triggerEvent) {
        return beforeState.name() + ":" + triggerEvent.name();
    }
}

最后编写一个 FsmFactory,在 Spring 项目启动时初始化各个类型的状态机,并对外提供统一状态机驱动接口;

java
/**
 * 状态机工厂
 */
@SuppressWarnings("all")
public class FsmFactory {

    /**
     * 状态机类型映射
     */
    private static final Map<Class<?>, Fsm> fsmMap = new HashMap<>();

    private static volatile boolean init = false;

    /**
     * 项目各个类型状态机初始化入口
     *
     * @param applicationContext 容器上下文
     */
    public static void init(ApplicationContext applicationContext) {
        Collection<IFsmTransition> transitions = applicationContext.getBeansOfType(IFsmTransition.class).values();
        MultiValueMap<Class, IFsmTransition> transitionMap = new LinkedMultiValueMap<>();
        for (IFsmTransition transition : transitions) {
            Class<?> stateType = GenericUtils.getInterfaceGeneric(transition, IFsmTransition.class, 2);
            FsmAssert.notNull(stateType, "状态机类型不能为空");
            transitionMap.add(stateType, transition);
        }
        transitionMap.forEach((stateType, transitionList) -> {
            Fsm fsm = new Fsm();
            fsm.init(transitionList);
            fsmMap.put(stateType, fsm);
        });
        init = true;
    }

    /**
     * 驱动状态转移
     *
     * @param context 状态机上下文
     */
    public static void fire(FsmContext context) {
        FsmAssert.isTrue(init, "状态机还未初始化");
        Class<?> stateType = context.getClass();
        Fsm fsm = fsmMap.get(stateType);
        FsmAssert.notNull(fsm, "状态机不存在," + stateType);
        fsm.fire(context);
    }
}

Spring Statemachine

Spring 状态机建立在有限状态机的概念上,是 Spring 框架的一部分,提供了在 Java 应用中定义和使用状态机的工具,用于简化状态机模型的开发。

引入 Spring Statemachine 依赖;

xml
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

定义状态转移规则;

java
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateFsmConfig extends StateMachineConfigurerAdapter<EOrderState, EOrderEvent> {

    @Override
    public void configure(StateMachineStateConfigurer<EOrderState, EOrderEvent> states) throws Exception {
        states
                .withStates()
                .initial(EOrderState.CREATED)
                .states(EnumSet.allOf(EOrderState.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<EOrderState, EOrderEvent> transitions) throws Exception {
        transitions
                .withExternal().source(EOrderState.CREATED).target(EOrderState.PAYED).event(EOrderEvent.PAY)
        ;
    }
}

监听状态转移,编写对应的 Action;

java
@WithStateMachine(name = "orderStateMachine")
public class OrderStateMachineListener {

    @Resource
    private OrderPayFsmAction payFsmAction;

    @OnTransition(source = "CREATED", target = "PAYED")
    public void payTransition(Message<EOrderEvent> message) {
        OrderFsmContext context = (OrderFsmContext) message.getHeaders().get("context");
        payFsmAction.execute(context);
    }
}

驱动状态转移示例;

java
stateMachine.start();
Message<EOrderEvent> message = MessageBuilder.withPayload(EOrderEvent.PAY).setHeader("context", fsmContext).build();
stateMachine.sendEvent(message);

Spring 状态机使用起来也是非常简单,并且支持状态的持久化,使用DSL(领域特定语言)风格的定义,使得状态机的配置更加直观和简单。

基于 MIT 许可发布