SpringBoot - 状态机框架StateMachine使用详解1(基本用法、监听器、Message)
作者:hangge | 2023-01-13 09:20
一、基本介绍
1,什么是状态机?
(1)状态机(State Machine)是一种软件设计模式,它可以帮助开发人员管理和控制系统中的状态变化。状态机通常用于描述系统的状态流转,并定义了状态之间的转换规则。
我们说状态机(State Machine)一般指有限状态机(Finite State Machine,简称 FSM)。有限状态机是一种特殊的状态机,通常用于描述有限个状态和有限个转换的系统。它的状态和转换是固定的,也就是说,系统只能处于有限个状态,且只能通过有限个转换来改变状态。
(2)状态机可归纳为 4 个要素,现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。
- 现态:指当前所处的状态
- 条件:又称“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必须的,当条件满足后,也可以不执行任何动作,直接迁移到新的状态。
- 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转换成“现态”。
2,什么是 Spring StateMachine?
(1)Spring StateMachine 是一个基于 Spring 框架的状态机框架,使用 Spring StateMachine 可以方便地在 Java 应用中实现状态机功能,并可以与 Spring 框架的其他功能结合使用。
(2)Spring StateMachine 提供了一组简单易用的 API,可以方便地定义状态机的状态、事件和转换。可以使用 Spring 的配置文件来定义状态机的结构,也可以使用 Java 代码来定义。
(3)Spring StateMachine 还提供了一些丰富的功能,例如支持使用拦截器对状态机的事件进行拦截,以及支持使用监听器来监听状态机的状态变化事件。
3,使用 Spring StateMachine 的步骤
- 步骤1:定义状态枚举和事件枚举
- 步骤2:定义状态机的初始状态和所有状态
- 步骤3:定义状态之间的转移规则
- 步骤4:在业务对象中使用状态机,编写响应状态变化的监听器方法
二、基本用法
1,添加依赖
要使用 Spring StateMachine 框架,首先编辑项目的 pom.xml 文件,添加相关依赖:
<!--加入spring statemachine的依赖--> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>2.5.0</version> </dependency>
2,创建订单状态枚举
这里以订货流程来演示状态机的使用。我们创建一个订单状态枚举类,其中共有三个状态(待支付、待收货、结束)。
// 订单状态枚举 public enum States { UNPAID, // 待支付 WAITING_FOR_RECEIVE, // 待收货 DONE // 结束 }
3,创建订单事件枚举
然后创建一个订单事件枚举类,里面包含两个引起状态迁移的事件(支付、收货)。
// 订单事件枚举 public enum Events { PAY, // 支付 RECEIVE // 收货 }
4,创建状态机配置
状态机配置类中包含状态配置、状态转换事件关系配置、监听器配置。
注意:监听器仅仅是负责监听而已,它无法对状态转移流程进行控制。也就是说即使监听器内部代码抛出异常,状态仍然会照常发生变化。如果需要通过业务代码控制状态是否转移、转移分支,需要使用后文介绍的 guard、action、choice。
// 状态机的配置类 @Configuration //该注解用来启用Spring StateMachine状态机功能 @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { // 初始化当前状态机拥有哪些状态 @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates().initial(States.UNPAID) //定义了初始状态为UNPAID待支付 .states(EnumSet.allOf(States.class)); //指定States中的所有状态作为该状态机的状态定义 } // 初始化当前状态机有哪些状态迁移动作 // 有来源状态为source,目标状态为target,触发事件为event @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.UNPAID).target(States.WAITING_FOR_RECEIVE) .event(Events.PAY) //支付事件将触发:待支付状态->待收货状态 .and() .withExternal() .source(States.WAITING_FOR_RECEIVE).target(States.DONE) .event(Events.RECEIVE); // 收货事件将触发:待收货状态->结束状态 } // 初始化当前状态机配置 @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config.withConfiguration().listener(listener()); // 设置监听器 } // 监听器实例 @Bean public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { // 在状态机的状态转换时调用 @Override public void transition(Transition<States, Events> transition) { // 当前是未支付状态 if(transition.getTarget().getId() == States.UNPAID) { System.out.println("订单创建"); return; } // 从未支付->待收货状态 if(transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { System.out.println("用户支付完毕"); return; } // 从待收货->完成状态 if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE && transition.getTarget().getId() == States.DONE) { System.out.println("用户已收货"); return; } } }; } }
5,整体流程测试
(1)最后测试一下,我们定义了整个流程的处理过程:
- start() 就是创建这个订单流程,根据之前的定义,该订单会处于待支付状态。
- 然后通过调用 sendEvent(Events.PAY) 执行支付操作
- 最后通过掉用 sendEvent(Events.RECEIVE) 来完成收货操作。
@SpringBootApplication public class TestApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } // 状态机对象 @Autowired private StateMachine<States, Events> stateMachine; //在run函数中,我们定义了整个流程的处理过程 @Override public void run(String... args) throws Exception { System.out.println("--- 开始创建订单流程 ---"); stateMachine.start(); //start()就是创建这个订单流程,根据之前的定义,该订单会处于待支付状态, System.out.println("> 当前状态:" + stateMachine.getState().getId()); System.out.println("--- 发送支付事件 ---"); boolean result1 = stateMachine.sendEvent(Events.PAY); System.out.println("> 事件是否发送成功:" + result1 + ",当前状态:" + stateMachine.getState().getId()); System.out.println("--- 再次发送支付事件 ---"); boolean result2 = stateMachine.sendEvent(Events.PAY); System.out.println("> 事件是否发送成功:" + result2 + ",当前状态:" + stateMachine.getState().getId()); System.out.println("--- 发送收货事件 ---"); boolean result3 = stateMachine.sendEvent(Events.RECEIVE); System.out.println("> 事件是否发送成功:" + result3 + ",当前状态:" + stateMachine.getState().getId()); } }
(2)运行结果如下。要注意的是,上面代码我们连续发送两个 PAY 支付事件,结果只有第一次发送成功。因为此时状态已经转移到了待收货状态,不能再次接收支付事件。
三、状态监听器
1,所有事件监听
(1)上面样例中我们仅仅监听了状态转移事件,其实状态监听器可以实现的功能远不止上面我们所述的内容,它还有更多的事件捕获,我们可以通过查看 StateMachineListener 接口来了解它所有的事件定义:
public interface StateMachineListener<S, E> { // 在状态机的状态改变时调用。 void stateChanged(State<S, E> var1, State<S, E> var2); // 在状态机进入新状态时调用 void stateEntered(State<S, E> var1); // 状态机离开旧状态时调用 void stateExited(State<S, E> var1); // 在状态机不能接受某个事件时调用 void eventNotAccepted(Message<E> var1); // 在状态机的状态转换时调用 void transition(Transition<S, E> var1); // 在状态机开始进行状态转换时调用 void transitionStarted(Transition<S, E> var1); // 在状态机完成状态转换时调用。 void transitionEnded(Transition<S, E> var1); // 在状态机开始运行时被调用。 void stateMachineStarted(StateMachine<S, E> var1); // 在状态机停止运行时被调用。 void stateMachineStopped(StateMachine<S, E> var1); // 在状态机发生错误时被调用。参数exception是表示错误的异常对象。 void stateMachineError(StateMachine<S, E> var1, Exception var2); // 在状态机的扩展状态的改变时被调用。 void extendedStateChanged(Object var1, Object var2); void stateContext(StateContext<S, E> var1); }
(2)下面代码我们又增加了状态机开始进行状态转换、和完成状态转换者两个事件监听:
// 状态机的配置类 @Configuration //该注解用来启用Spring StateMachine状态机功能 @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { // 初始化当前状态机拥有哪些状态 @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates().initial(States.UNPAID) //定义了初始状态为UNPAID待支付 .states(EnumSet.allOf(States.class)); //指定States中的所有状态作为该状态机的状态定义 } // 初始化当前状态机有哪些状态迁移动作 // 有来源状态为source,目标状态为target,触发事件为event @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.UNPAID).target(States.WAITING_FOR_RECEIVE) .event(Events.PAY) //支付事件将触发:待支付状态->待收货状态 .and() .withExternal() .source(States.WAITING_FOR_RECEIVE).target(States.DONE) .event(Events.RECEIVE); // 收货事件将触发:待收货状态->结束状态 } // 初始化当前状态机配置 @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config.withConfiguration().listener(listener()); // 设置监听器 } // 监听器实例 @Bean public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { // 在状态机的状态转换时调用 @Override public void transition(Transition<States, Events> transition) { // 当前是未支付状态 if(transition.getTarget().getId() == States.UNPAID) { System.out.println("订单创建"); return; } // 从未支付->待收货状态 if(transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { System.out.println("用户支付完毕"); return; } // 从待收货->完成状态 if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE && transition.getTarget().getId() == States.DONE) { System.out.println("用户已收货"); return; } } // 在状态机开始进行状态转换时调用 @Override public void transitionStarted(Transition<States, Events> transition) { // 从未支付->待收货状态 if(transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { System.out.println("用户支付(状态转换开始)"); return; } } // 在状态机进行状态转换结束时调用 @Override public void transitionEnded(Transition<States, Events> transition) { // 从未支付->待收货状态 if(transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { System.out.println("用户支付(状态转换结束)"); return; } } }; } }
2,使用注解方式配置监听器
(1)对于状态监听器,Spring StateMachine 还提供了优雅的注解配置实现方式,所有 StateMachineListener 接口中定义的事件都能通过注解的方式来进行配置实现。比如,我们可以将上面样例中的状态监听器改用如下注解配置:
注意:监听器仅仅是负责监听而已,它无法对状态转移流程进行控制。也就是说即使监听器内部代码抛出异常,状态仍然会照常发生变化。如果需要通过业务代码控制状态是否转移、转移分支,需要使用后文介绍的 guard、action、choice。
//事件监听器 @Configuration @WithStateMachine public class StateMachineEventConfig { @OnTransition(target = "UNPAID") public void create() { System.out.println("订单创建"); } @OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE") public void pay() { System.out.println("用户支付完毕"); } @OnTransitionStart(source = "UNPAID", target = "WAITING_FOR_RECEIVE") public void payStart() { System.out.println("用户支付(状态转换开始)"); } @OnTransitionEnd(source = "UNPAID", target = "WAITING_FOR_RECEIVE") public void payEnd() { System.out.println("用户支付(状态转换结束)"); } @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE") public void receive() { System.out.println("用户已收货"); } }
(2)这样原先状态机的配置类里面的代码就十分简洁了:
// 状态机的配置类 @Configuration //该注解用来启用Spring StateMachine状态机功能 @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { // 初始化当前状态机拥有哪些状态 @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates().initial(States.UNPAID) //定义了初始状态为UNPAID待支付 .states(EnumSet.allOf(States.class)); //指定States中的所有状态作为该状态机的状态定义 } // 初始化当前状态机有哪些状态迁移动作 // 有来源状态为source,目标状态为target,触发事件为event @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.UNPAID).target(States.WAITING_FOR_RECEIVE) .event(Events.PAY) //支付事件将触发:待支付状态->待收货状态 .and() .withExternal() .source(States.WAITING_FOR_RECEIVE).target(States.DONE) .event(Events.RECEIVE); // 收货事件将触发:待收货状态->结束状态 } }
四、发送 Message 消息
1,发送消息
(1)Statemachine 的 sendEvent() 方法除了可以发送事件外,还可以发送 Message。Message 除了会传递事件从而触发状态机的转换外,同时还能传递其他信息方便我们进行业务处理。假设我们需要传递订单信息,首先定义一个如下订单类:
// 订单类 public class Order { // 订单号 private int id; // 订单状态 private States states; public void setStates(States states) { this.states = states; } public States getStates() { return states; } public void setId(int id) { this.id = id; } public int getId() { return id; } @Override public String toString() { return "订单号:" + id + ", 订单状态:" + states; } }
(2)然后使用 MessageBuilder 来创建 Message 实例,并设置 Message 的各种属性。最后通过 sendEvent() 方法发送即可:
@SpringBootApplication public class TestApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } // 状态机对象 @Autowired private StateMachine<States, Events> stateMachine; //在run函数中,我们定义了整个流程的处理过程 @Override public void run(String... args) throws Exception { System.out.println("--- 开始创建订单流程 ---"); stateMachine.start(); //start()就是创建这个订单流程,根据之前的定义,该订单会处于待支付状态, // 创建订单对象 Order order = new Order(); order.setStates(States.UNPAID); order.setId(123); System.out.println("--- 发送支付事件 ---"); Message message = MessageBuilder.withPayload(Events.PAY) .setHeader("order", order).build(); // 构建消息 stateMachine.sendEvent(message); System.out.println("--- 发送收货事件 ---"); message = MessageBuilder.withPayload(Events.RECEIVE) .setHeader("order", order).build(); // 构建消息 stateMachine.sendEvent(message); } }
2,接收消息
(1)如果是使用注解方式配置的监听器,可以通过如下方式接收发送的消息://事件监听器 @Configuration @WithStateMachine public class StateMachineEventConfig { @OnTransition(target = "UNPAID") public void create() { System.out.println("订单创建"); } @OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE") public void pay(Message<Events> message) { // 获取消息中的订单对象 Order order = (Order) message.getHeaders().get("order"); // 设置新状态 order.setStates(States.WAITING_FOR_RECEIVE); System.out.println("用户支付完毕,状态机反馈信息:" + message.getHeaders().toString()); } @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE") public void receive(Message<Events> message) { // 获取消息中的订单对象 Order order = (Order) message.getHeaders().get("order"); // 设置新状态 order.setStates(States.DONE); System.out.println("用户已收货,状态机反馈信息:" + message.getHeaders().toString()); } }
(2)如何是原始方式的话就会稍微麻烦些,我们需要在 StateMachineListener 中使用它的 stateContext 方法来获取 StateContext。然后通过 StateContext 的 getMessageHeaders 方法来访问消息头。
// 状态机的配置类 @Configuration //该注解用来启用Spring StateMachine状态机功能 @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { // 初始化当前状态机拥有哪些状态 @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates().initial(States.UNPAID) //定义了初始状态为UNPAID待支付 .states(EnumSet.allOf(States.class)); //指定States中的所有状态作为该状态机的状态定义 } // 初始化当前状态机有哪些状态迁移动作 // 有来源状态为source,目标状态为target,触发事件为event @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.UNPAID).target(States.WAITING_FOR_RECEIVE) .event(Events.PAY) //支付事件将触发:待支付状态->待收货状态 .and() .withExternal() .source(States.WAITING_FOR_RECEIVE).target(States.DONE) .event(Events.RECEIVE); // 收货事件将触发:待收货状态->结束状态 } // 初始化当前状态机配置 @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config.withConfiguration().listener(listener()); // 设置监听器 } // 监听器实例 @Bean public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { // 保存当前的状态上下文 private StateContext stateContext; // 上下文状态改变时调用 @Override public void stateContext(StateContext<States, Events> stateContext) { this.stateContext = stateContext; } // 在状态机的状态转换时调用 @Override public void transition(Transition<States, Events> transition) { // 当前是未支付状态 if(transition.getTarget().getId() == States.UNPAID) { System.out.println("订单创建"); return; } // 从未支付->待收货状态 if(transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { // 获取触发转换的消息 Message<Events> message = this.stateContext.getMessage(); // 获取消息中的订单对象 Order order = (Order) message.getHeaders().get("order"); // 设置新状态 order.setStates(States.WAITING_FOR_RECEIVE); System.out.println("用户支付完毕,状态机反馈信息:" + message.getHeaders().toString()); return; } // 从待收货->完成状态 if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE && transition.getTarget().getId() == States.DONE) { // 获取触发转换的消息 Message<Events> message = this.stateContext.getMessage(); // 获取消息中的订单对象 Order order = (Order) message.getHeaders().get("order"); // 设置新状态 order.setStates(States.DONE); System.out.println("用户已收货,状态机反馈信息:" + message.getHeaders().toString()); return; } } }; } }
全部评论(0)