状态机简介
状态机(State Machine)是一种计算模型,用于描述系统在不同状态下的行为以及状态之间的转移。
状态机通常包括以下几个要素:
状态(States): 描述系统可能处于的不同状态。每个状态代表系统在某个时间点的特定情况或条件。
转移(Transitions): 描述系统从一个状态到另一个状态的条件和动作。当满足一定条件时,系统就会根据定义好的转移规则从一个状态转移到另一个状态。
事件(Events): 触发状态转移的外部或内部事件。这些事件可以是用户的输入、系统的内部触发、或者其他外部条件的改变。
动作(Actions): 在状态转移发生时执行的操作或任务。这些动作可以包括更新变量、触发其他事件、或执行一些计算。
根据状态数量优有限还是无限,可以将状态机分成有限状态机(Finite State Machine,FSM)和无限状态机(Infinite State Machine),通常我们讨论的都是 FSM。
以下是一个订单状态机的状态转移截取示意;
使用优势
难以想象一个多状态转移的业务流程如果不使用状态机,编码会有多么错乱且不易维护。
以下是使用状态机的优势;
清晰的模型: 状态机提供了一种直观、清晰的建模方式,使得系统的行为和状态转换能够被更容易理解和可视化。这有助于开发人员和团队共同理解系统的设计和逻辑。
易于调试和测试: 由于状态机的行为是基于状态和转移规则定义的,因此在调试和测试过程中,可以更容易地定位和修复问题。开发人员可以追踪系统的状态变化,检查是否符合预期。
可扩展性: 状态机是一种模块化的设计方法,可以通过添加新的状态和相应的状态转移规则来扩展系统。这使得系统更易于扩展,同时保持代码的整洁性。
可读性和维护性: 由于状态机提供了一种高度抽象的描述方式,使得代码更易于阅读和理解。这种清晰性有助于降低代码的维护成本。
状态机框架广泛支持: 许多编程语言和框架都提供了状态机的实现或库,使得开发人员可以更轻松地集成状态机模型到他们的应用程序中。
Java 实践:简易状态机
基于状态机的四要素,用 Java 基于 Spring 实现一个简易状态机框架。首先定义状态机上下文,便于状态转移过程中的数据处理;
/**
* 状态机上下文
*
* @param <P> 状态类型枚举
* @param <Q> 事件类型枚举
*/
@Data
public class FsmContext<P extends Enum<P>, Q extends Enum<Q>> {
/**
* 当前状态
*/
private P currentState;
/**
* 触发事件
*/
private Q triggerEvent;
}
框架需要足够简单,Transition 定义好转移条件,和执行动作;
/**
* 状态转移接口
*
* @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);
}
再定义状态机实体,包含状态机初始化方法和驱动状态转移;
/**
* 简易状态机
*
* @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 项目启动时初始化各个类型的状态机,并对外提供统一状态机驱动接口;
/**
* 状态机工厂
*/
@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 依赖;
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
定义状态转移规则;
@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;
@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);
}
}
驱动状态转移示例;
stateMachine.start();
Message<EOrderEvent> message = MessageBuilder.withPayload(EOrderEvent.PAY).setHeader("context", fsmContext).build();
stateMachine.sendEvent(message);
Spring 状态机使用起来也是非常简单,并且支持状态的持久化,使用DSL(领域特定语言)风格的定义,使得状态机的配置更加直观和简单。