Spring Statemachine 是应用程序开发人员在 Spring 应用程序中使用状态机概念的框架,从设计层面分析:状态机目的是解决复杂的状态管理流程,保证程序单一原则和开闭原则;业务角度分析:状态机应有初始化状态、加载所有已有的状态、事件、转移、动作、触发下一个状态为驱动,并解决了业务逻辑与状态管理之间的强耦合。
状态机很强大,因为行为总是被保证一致,使得调试相对容易。这是因为操作规则是在机器启动时一成不变的。这个想法是您的应用程序可能存在于有限数量的状态中,并且某些预定义的触发器可以将您的应用程序从一个状态带到下一个状态。此类触发器可以基于事件或计时器。
在应用程序之外定义高级逻辑然后依靠状态机来管理状态要容易得多。您可以通过发送事件、监听更改或简单地请求当前状态来与状态机交互。
org.springframework.statemachine
spring-statemachine-core
2.1.2.RELEASE
public enum OrderStatus {
WAIT_PAYMENT, // 待付款
WAIT_DELIVER, // 待发货
WAIT_RECEIVE, // 待收货
FINISH, // 已收货
WAIT_COMMENT, // 待评论
COMMENTED, // 已评论
UNCOMMENTED; // 未评论
}
public enum ChangeEvent {
PAYED, // 支付
DELIVERY, // 发货
RECEIVED, // 收货
COMMENT; // 评价
}
@Configuration
@EnableStateMachine
@Slf4j
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, ChangeEvent> {
@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);
}
}
@Component
@Slf4j
public class OrderStateListenerImpl extends StateMachineListenerAdapter<OrderStatus, ChangeEvent> {
@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));
log.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));
log.info(stringBuilder.toString());
}
}
@SpringBootApplication
@Slf4j
public class FsmApplication implements CommandLineRunner {
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(110350339917373440L);
orderInfo.setDesc("test order");
Message<ChangeEvent> message = null;
log.info("current state {}", stateMachine.getState().getId().name());
// spring message的payload设置为消息事件、header为额外需要带的参数
message = MessageBuilder.withPayload(ChangeEvent.PAYED).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
log.info("current state {}", stateMachine.getState().getId().name());
message = MessageBuilder.withPayload(ChangeEvent.DELIVERY).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
log.info("current state {}", stateMachine.getState().getId().name());
message = MessageBuilder.withPayload(ChangeEvent.RECEIVED).setHeader("order", orderInfo).build();
stateMachine.sendEvent(message);
log.info("current state {}", stateMachine.getState().getId().name());
}
}