玩转Spring状态机

金歌  金牌会员 | 2024-2-27 12:51:57 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 937|帖子 937|积分 2811

说起Spring状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring状态机就是状态模式的一种实现,在介绍Spring状态机之前,让我们来看看设计模式中的状态模式。
1. 状态模式

状态模式的定义如下:
状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为。在状态模式中,一个对象的行为取决于其当前状态,而且可以随时改变这个状态。状态模式将对象的状态封装在不同的状态类中,从而使代码更加清晰和易于维护。当一个对象的状态改变时,状态模式会自动更新该对象的行为,而不需要在代码中手动进行判断和处理。
通常业务系统中会存在一些拥有状态的对象,而且这些状态之间可以进行转换,并且在不同的状态下会表现出不同的行为或者不同的功能,比如交通灯控制系统中会存在红灯、绿灯和黄灯,再比如订单系统中的订单会存在已下单、待支付、待发货、待收货等状态,这些状态会通过不同的行为进行相互转换,这时候在系统设计时就可以使用状态模式。
下面是状态模式类图:

可以看到状态模式主要包含三种类型的角色:
1、上下文(**Context**)角色:封装了状态的实例,负责维护状态实例,并将请求委托给当前的状态对象。
2、抽象状态(**State**)角色:定义了表示不同状态的接口,并封装了该状态下的行为。所有具体状态都实现这个接口。
3、具体状态(**Concrete State**)角色:具体实现了抽象状态角色的接口,并封装了该状态下的行为。
下面是使用状态模式实现红绿灯状态变更的一个简单案例:
抽象状态类:
  1. /**
  2. * @description: 抽象状态类
  3. */
  4. public abstract class MyState {
  5.     abstract void handler();
  6. }
复制代码
具体状态类A
  1. /**
  2. * @description: 具体状态A
  3. */
  4. public class RedLightState extends MyState{
  5.     @Override
  6.     void handler() {
  7.         System.out.println("红灯停");
  8.     }
  9. }
复制代码
具体状态类B
  1. /**
  2. * @description: 具体状态B
  3. */
  4. public class GreenLightState extends MyState{
  5.     @Override
  6.     void handler() {
  7.         System.out.println("绿灯行");
  8.     }
  9. }
复制代码
环境类:维护当前状态对象,并提供了切换状态的方法。
  1. /**
  2. * @description: 环境类
  3. */
  4. public class MyContext {
  5.     private MyState state;
  6.     public void setState(MyState state) {
  7.         this.state = state;
  8.     }
  9.     public void handler() {
  10.         state.handler();
  11.     }
  12. }
复制代码
测试类
  1. /**
  2. * @description: 测试状态模式
  3. */
  4. public class TestStateModel {
  5.     public static void main(String[] args) {
  6.         MyContext myContext = new MyContext();
  7.         RedLightState redLightState = new RedLightState();
  8.         GreenLightState greenLightState = new GreenLightState();
  9.         myContext.setState(redLightState);
  10.         myContext.handler(); //红灯停
  11.         myContext.setState(greenLightState);
  12.         myContext.handler(); //绿灯行
  13.     }
  14. }
复制代码
下面是对应的执行结果

可以发现,使用状态模式中的状态类在一定程度上也消除了if-else逻辑校验,看到这里, 有些人可能会有疑问:状态模式和策略模式的区别是什么呢?
状态模式更关注对象在不同状态的行为和状态之间的流转,而策略模式更关注对象不同策略的选择。
上面我们介绍了设计模式中的状态模式,接下来我们来看看Spring状态机。
2. Spring状态机

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型 。说白了,就是指一张状态转换图。 状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring也提供了一个很好的解决方案。Spring中的组件名称就叫作状态机(StateMachine)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。
通过定义,我们很容易分析得到状态机应当具备一下几个要素:

  • 当前状态:也就是状态流转的起始状态。
  • 触发事件:引起状态之间流转的一些列动作。
  • 响应函数:触发事件到下一个状态之间的规则。
  • 目标状态:状态流转的目标状态。
对于组件化的状态机,当前使用较多的主要是两种:一种是Spring 状态机,一种是COLA状态机,这两种状态机的对比如下表所示:
Spring 状态机COLA 状态机API 调用使用 Reactive 的 Mono、Flux 方式进行 API 调用同步的 API 调用,如果有需要也可以将方法通过 消息队列、定时任务、多线程等方式进行异步调用代码量core 包 284 个接口和类36 个接口和类生态非常丰富较为贫瘠定制化难度困难简单可以看到,Spring状态机锁提供的内容较为丰富,当然对于自定义的支持就不如COLA状态机好,如果对自定义的需求比较高,那建议使用COLA状态机。
本文以Spring状态机为例,展示如何在业务系统中使用状态机。
为了便于大家了解Spring状态机的实现原理和使用方式以及其提供的功能,下面列出了官方文档和源码,感兴趣的同学可以阅读阅读。
官方文档: https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-states
源代码: https://github.com/spring-projects/spring-statemachine
3. Spring状态机实现订单状态流转

对于状态模式,Spring封装好了一个组件,就叫状态机(StateMachine)。Spring状态机可以帮助我们开发者简化状态控制的开发过程,让状态机结构更加层次化。下面用Spring状态机模拟一个订单状态流转的过程。
3.1 环境准备

首先,如果要使用spring状态机,需要引入对应的jar包,这里我的springboot版本是:2.2.1.RELEASE
  1. <dependency>
  2.     <groupId>org.springframework.statemachine</groupId>
  3.     <artifactId>spring-statemachine-core</artifactId>
  4.     <version>${springboot.version}</version>
  5. </dependency>
复制代码
下面是简化的订单的定义,以及订单状态和订单转换行为的枚举
  1. /**
  2. * @description: 模拟订单类
  3. */
  4. @Data
  5. public class Order {
  6.     private Long orderId;
  7.     private OrderStatusEnum orderStatus;
  8. }
  9. /**
  10. * @description: 订单状态
  11. */
  12. public enum OrderStatusEnum {
  13.     // 待支付
  14.     WAIT_PAYMENT,
  15.     // 待发货
  16.     WAIT_DELIVER,
  17.     // 待收货
  18.     WAIT_RECEIVE,
  19.     // 完成
  20.     FINISH;
  21. }
  22. /**
  23. * @description:订单状态转换行为
  24. */
  25. public enum OrderStatusChangeEventEnum {
  26.     //支付
  27.     PAYED,
  28.     //发货
  29.     DELIVERY,
  30.     //收货
  31.     RECEIVED;
  32. }
复制代码
3.2 构造订单状态机

在引入jar包之后,需要构建一个针对订单状态流转的状态机
订单状态机配置类如下:
  1. /**
  2. * @description: 订单状态机
  3. */
  4. @Configuration
  5. @EnableStateMachine
  6. public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {
  7.     /**
  8.      * 配置状态
  9.      */
  10.     @Override
  11.     public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {
  12.         states.withStates()
  13.                 .initial(OrderStatusEnum.WAIT_PAYMENT)
  14.                 .end(OrderStatusEnum.FINISH)
  15.                 .states(EnumSet.allOf(OrderStatusEnum.class));
  16.     }
  17.     /**
  18.      * 配置状态转换事件关系
  19.      */
  20.     @Override
  21.     public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> transitions) throws Exception {
  22.         transitions.withExternal().source(OrderStatusEnum.WAIT_PAYMENT).target(OrderStatusEnum.WAIT_DELIVER)
  23.                 .event(OrderStatusChangeEventEnum.PAYED)
  24.                 .and()
  25.                 .withExternal().source(OrderStatusEnum.WAIT_DELIVER).target(OrderStatusEnum.WAIT_RECEIVE)
  26.                 .event(OrderStatusChangeEventEnum.DELIVERY)
  27.                 .and()
  28.                 .withExternal().source(OrderStatusEnum.WAIT_RECEIVE).target(OrderStatusEnum.FINISH)
  29.                 .event(OrderStatusChangeEventEnum.RECEIVED);
  30.     }
  31. }
复制代码
3.3 编写状态机监听器

监听状态变更事件,完成状态转换。
  1. /**
  2. * @description: 状态监听
  3. */
  4. @Component
  5. @WithStateMachine
  6. @Transactional
  7. public class OrderStatusListener {
  8.     @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
  9.     public boolean payTransition(Message message) {
  10.         Order order = (Order) message.getHeaders().get("order");
  11.         order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);
  12.         System.out.println("支付,状态机反馈信息:" + message.getHeaders().toString());
  13.         return true;
  14.     }
  15.     @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
  16.     public boolean deliverTransition(Message message) {
  17.         Order order = (Order) message.getHeaders().get("order");
  18.         order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);
  19.         System.out.println("发货,状态机反馈信息:" + message.getHeaders().toString());
  20.         return true;
  21.     }
  22.     @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
  23.     public boolean receiveTransition(Message message) {
  24.         Order order = (Order) message.getHeaders().get("order");
  25.         order.setOrderStatus(OrderStatusEnum.FINISH);
  26.         System.out.println("收货,状态机反馈信息:" + message.getHeaders().toString());
  27.         return true;
  28.     }
  29. }
复制代码
3.4 编写订单服务类

模拟对订单的一些业务操作
  1. /**
  2. * @description: 订单服务
  3. */
  4. @Service
  5. public class OrderServiceImpl implements OrderService {
  6.     @Resource
  7.     private StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;
  8.     private long id = 1L;
  9.     private Map<Long, Order> orders = Maps.newConcurrentMap();
  10.     @Override
  11.     public Order create() {
  12.         Order order = new Order();
  13.         order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);
  14.         order.setOrderId(id++);
  15.         orders.put(order.getOrderId(), order);
  16.         System.out.println("订单创建成功:" + order.toString());
  17.         return order;
  18.     }
  19.     @Override
  20.     public Order pay(long id) {
  21.         Order order = orders.get(id);
  22.         System.out.println("尝试支付,订单号:" + id);
  23.         Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).
  24.                 setHeader("order", order).build();
  25.         if (!sendEvent(message)) {
  26.             System.out.println(" 支付失败, 状态异常,订单号:" + id);
  27.         }
  28.         return orders.get(id);
  29.     }
  30.     @Override
  31.     public Order deliver(long id) {
  32.         Order order = orders.get(id);
  33.         System.out.println(" 尝试发货,订单号:" + id);
  34.         if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY)
  35.                 .setHeader("order", order).build())) {
  36.             System.out.println(" 发货失败,状态异常,订单号:" + id);
  37.         }
  38.         return orders.get(id);
  39.     }
  40.     @Override
  41.     public Order receive(long id) {
  42.         Order order = orders.get(id);
  43.         System.out.println(" 尝试收货,订单号:" + id);
  44.         if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED)
  45.                 .setHeader("order", order).build())) {
  46.             System.out.println(" 收货失败,状态异常,订单号:" + id);
  47.         }
  48.         return orders.get(id);
  49.     }
  50.     @Override
  51.     public Map<Long, Order> getOrders() {
  52.         return orders;
  53.     }
  54.     /**
  55.      * 发送状态转换事件
  56.      * @param message
  57.      * @return
  58.      */
  59.     private synchronized boolean sendEvent(Message<OrderStatusChangeEventEnum> message) {
  60.         boolean result = false;
  61.         try {
  62.             orderStateMachine.start();
  63.             result = orderStateMachine.sendEvent(message);
  64.         } catch (Exception e) {
  65.             e.printStackTrace();
  66.         } finally {
  67.             if (Objects.nonNull(message)) {
  68.                 Order order = (Order) message.getHeaders().get("order");
  69.                 if (Objects.nonNull(order) && Objects.equals(order.getOrderStatus(), OrderStatusEnum.FINISH)) {
  70.                     orderStateMachine.stop();
  71.                 }
  72.             }
  73.         }
  74.         return result;
  75.     }
  76. }
复制代码
3.5 测试入口

这里编写一个controller模拟c端用户请求,为了便于展示,这里使用一个测试方法完成所有的操作
  1. @RestController
  2. public class OrderController {
  3.     @Resource
  4.     private OrderService orderService;
  5.     @RequestMapping("/testOrderStatusChange")
  6.     public String testOrderStatusChange(){
  7.         orderService.create();
  8.         orderService.create();
  9.         orderService.pay(1L);
  10.         orderService.deliver(1L);
  11.         orderService.receive(1L);
  12.         orderService.pay(2L);
  13.         orderService.deliver(2L);
  14.         orderService.receive(2L);
  15.         System.out.println("全部订单状态:" + orderService.getOrders());
  16.         return "success";
  17.     }
  18. }
复制代码
下面是对应的执行结果

可以看到spring状态机很好的控制了订单在各个状态之间的流转。
4. 思考与总结

思考:针对状态机的特点,还有其他思路实现一个状态机吗?下面是一些常规思路,如果还有其他方法欢迎在评论区留言。
1. 消息队列方式
订单状态的流转可以通过MQ发布一个事件,消费者根据业务条件把订单状态进行流转,可以根据不同的事件发送到不同的Topic。
2. 定时任务驱动
每隔一段时间启动一下job,根据特定的状态从数据库中拿对应的订单记录,然后判断订单是否有条件到达下一个状态。
3. 规则引擎方式
业务团队可以在规则引擎里编写一系列的状态及其对应的转换规则,由规则引擎根据已经加载的规则对输入数据进行解析,根据解析的结果执行相应的动作,完成状态流转。
总结:
本文主要介绍了设计模式中的状态模式,并在此基础上介绍了Spring状态机相关的概念,并根据常见的订单流转场景,介绍了Spring状态机的使用方式。文中如有不当之处,欢迎在评论区批评指正。
5. 参考内容

https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-states
https://cloud.tencent.com/developer/article/2198477?areaId=106001
https://cloud.tencent.com/developer/article/2360708?areaId=106001
https://juejin.cn/post/7087064901553750030
https://my.oschina.net/u/4090830/blog/10092135
https://juejin.cn/post/7267506576448929811
作者:京东科技 孙扬威
来源:京东云开发者社区 转载请注明来源

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表