开篇
- 这篇文章的目的是想实践下状态机的一些概念,一是加深下状态机的理解,而是接触下Spring Statemachine的一些概念。
- 整个东西接触下来也就半天的时间,从找资料理解到写demo代码,还是比较轻松的,就是理解的深入暂时还不够,但是能够对状态机有了更深的印象。
有限状态机
-
有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。应用FSM模型可以帮助对象生命周期的状态的顺序以及导致状态变化的事件进行管理。将状态和事件控制从不同的业务Service方法的if else中抽离出来。FSM的应用范围很广,对于有复杂状态流,扩展性要求比较高的场景都可以使用该模型。下面是状态机模型中的4个要素,即现态、条件、动作、次态。
-
现态:是指当前所处的状态。
-
条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
-
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
-
次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
Spring Statemachine
- Spring Statemachine通过简单的configure能够构建出状态迁移图,可以方便我们理解状态机的迁移。
定义状态
public enum OrderStatus {
WAIT_PAYMENT, // 待付款
WAIT_DELIVER, // 待发货
WAIT_RECEIVE, // 待收货
FINISH, // 已收货
WAIT_COMMENT, // 待评论
COMMENTED, // 已评论
UNCOMMENTED; // 未评论
}
定义事件
public enum ChangeEvent {
PAYED, // 支付
DELIVERY, // 发货
RECEIVED, // 收货
COMMENT; // 评价
}
状态转移
@Configuration
@EnableStateMachine(name = "stateMachine")
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, ChangeEvent> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private OrderStateListenerImpl orderStateListener;
@Resource
private PayedAction payedAction;
@Resource
private DeliveryAction deliveryAction;
@Resource
private ReceivedAction receivedAction;
@Resource
private CommentedAction commentedAction;
@Resource
private UncommentedAction uncommentedAction;
@Resource
private DeliveryGuard deliveryGuard;
@Resource
private PayedGuard payedGuard;
@Resource
private ReceivedGuard receivedGuard;
@Resource
private CommentGuard commentGuard;
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, ChangeEvent> states) throws Exception {
states.withStates()
// 设置初始化状态
.initial(OrderStatus.WAIT_PAYMENT)
// 设置用于条件判断的状态
.choice(OrderStatus.FINISH)
// 绑定全部状态
.states(EnumSet.allOf(OrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, ChangeEvent> transitions) throws Exception {
// 1、withExternal 是当source和target不同时的写法
// 2、withInternal 当source和target相同时的串联写法,比如付款失败时,付款前及付款后都是待付款状态
// 3、withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
transitions
// 通过PAYED 实现由 WAIT_PAYMENT => WAIT_DELIVER 状态转移
.withExternal()
.source(OrderStatus.WAIT_PAYMENT)
.target(OrderStatus.WAIT_DELIVER)
.event(ChangeEvent.PAYED)
.guard(payedGuard)
.action(payedAction)
.and()
// 通过DELIVERY 实现由 WAIT_DELIVER => WAIT_RECEIVE 状态转移
.withExternal()
.source(OrderStatus.WAIT_DELIVER)
.target(OrderStatus.WAIT_RECEIVE)
.event(ChangeEvent.DELIVERY)
.guard(deliveryGuard)
.action(deliveryAction)
.and()
// 通过RECEIVED 实现由 WAIT_RECEIVE => FINISH 状态转移
.withExternal()
.source(OrderStatus.WAIT_RECEIVE)
.target(OrderStatus.FINISH)
.event(ChangeEvent.RECEIVED)
.guard(receivedGuard)
.action(receivedAction)
.and()
// Choice的状态选择,
// commentGuard的结果为true则执行first
// commentGuard的结果为true则执行then
.withChoice()
.source(OrderStatus.FINISH)
.first(OrderStatus.COMMENTED, commentGuard, commentedAction)
.then(OrderStatus.UNCOMMENTED, commentGuard, uncommentedAction)
.last(OrderStatus.WAIT_COMMENT);
}
@Override
public void configure(StateMachineConfigurationConfigurer<OrderStatus, ChangeEvent> config) throws Exception {
config.withConfiguration().listener(orderStateListener);
}
}
- withExternal 是当source和target不同时的写法,比如付款成功后状态发生的变化。
- withInternal 当source和target相同时的串联写法,比如付款失败后都是待付款状态。
- withExternal的source和target用于执行前后状态、event为触发的事件、guard判断是否执行action。同时满足source、target、event、guard的条件后执行最后的action。
- withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
- withChoice根据guard的判断结果执行first/then的逻辑。
- withChoice不需要发送事件来进行触发。
状态转移监听器
@Component
public class OrderStateListenerImpl extends StateMachineListenerAdapter<OrderStatus, ChangeEvent> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void stateChanged(State<OrderStatus, ChangeEvent> from, State<OrderStatus, ChangeEvent> to) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("stateChanged");
stringBuilder.append(" from " + (null != from ? from.getId().name() : null));
stringBuilder.append(" to " + (null != to ? to.getId().name() : null));
logger.info(stringBuilder.toString());
}
@Override
public void transition(Transition<OrderStatus, ChangeEvent> transition) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("transition");
stringBuilder.append(" kind " + (null != transition.getKind() ? transition.getKind().name() : null));
stringBuilder.append(" from " + (null != transition.getSource() ? transition.getSource().getId().name() : null));
stringBuilder.append(" to " + (null != transition.getTarget() ? transition.getTarget().getId().name() : null));
stringBuilder.append(" trigger " + (null != transition.getTrigger() ? transition.getTrigger().getEvent().name() : null));
logger.info(stringBuilder.toString());
}
}
启动服务
@SpringBootApplication
public class FsmApplication implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(getClass());
public static void main(String[] args) {
SpringApplication.run(FsmApplication.class, args);
}
@Resource
private StateMachine<OrderStatus, ChangeEvent> stateMachine;
@Override
public void run(String... args) throws Exception {
stateMachine.start();
// 测试状态机消息变更
messageTransfer();
stateMachine.stop();
}
private void messageTransfer() {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOid(123456789L);
orderInfo.setDesc("test order");
Message<ChangeEvent> message = null;
logger.info("current state {}", stateMachine.getState().getId().name());
// spring message 的payload设置为消息事件、header为额外需要带的参数
message = MessageBuilder.withPayload(ChangeEvent.PAYED).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
logger.info("current state {}", stateMachine.getState().getId().name());
message = MessageBuilder.withPayload(ChangeEvent.DELIVERY).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
logger.info("current state {}", stateMachine.getState().getId().name());
message = MessageBuilder.withPayload(ChangeEvent.RECEIVED).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
logger.info("current state {}", stateMachine.getState().getId().name());
}
}
##状态转移过程输出
transition kind INITIAL from null to WAIT_PAYMENT trigger null
stateChanged from null to WAIT_PAYMENT
current state WAIT_PAYMENT
PayedGuard payLoad "PAYED" messageHeader{"desc":"test order","oid":123456789}
PayedAction execute
transition kind EXTERNAL from WAIT_PAYMENT to WAIT_DELIVER trigger PAYED
stateChanged from WAIT_PAYMENT to WAIT_DELIVER
current state WAIT_DELIVER
DeliveryGuard payLoad "DELIVERY" messageHeader{"desc":"test order","oid":123456789}
DeliveryAction execute
transition kind EXTERNAL from WAIT_DELIVER to WAIT_RECEIVE trigger DELIVERY
stateChanged from WAIT_DELIVER to WAIT_RECEIVE
current state WAIT_RECEIVE
PayedGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789}
ReceivedAction execute
transition kind EXTERNAL from WAIT_RECEIVE to FINISH trigger RECEIVED
CommentGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789} flag 1
CommentGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789} flag 2
UncommentedAction execute
stateChanged from WAIT_RECEIVE to UNCOMMENTED
current state UNCOMMENTED
网友评论