项目终于用上了 Spring 状态机,非常优雅!

打印 上一主题 下一主题

主题 875|帖子 875|积分 2635

来源:https://www.duidaima.com/Group/Topic/JAVA/11942
1、什么是状态机

1.1 什么是状态

先来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个自动门,就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如自动门的状态就是两个 open 和 closed 。

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。例如,根据自动门的运行规则,我们可以抽象出下面这么一个图。
自动门有两个状态,open 和 closed ,closed 状态下,如果读取开门信号,那么状态就会切换为 open 。open 状态下如果读取关门信号,状态就会切换为 closed 。
状态机的全称是有限状态自动机,自动两个字也是包含重要含义的。给定一个状态机,同时给定它的当前状态以及输入,那么输出状态时可以明确的运算出来的。例如对于自动门,给定初始状态 closed ,给定输入“开门”,那么下一个状态时可以运算出来的。
这样状态机的基本定义我们就介绍完毕了。重复一下:状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。
1.2 四大概念

下面来给出状态机的四大概念。

  • 第一个是 State ,状态。一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。
  • 第二个是 Event ,事件。事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。
  • 第三个是 Action ,动作。事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action一般就对应一个函数。
  • 第四个是 Transition ,变换。也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。
1.3 状态机

有限状态机(Finite-state machine,FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。
其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
2、状态机图

做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,我们就可以完成一个状态机图了:
以订单为例:以从待支付状态转换为待发货状态为例


  • ①现态:是指当前所处的状态。待支付
  • ②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。支付事件
  • ③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。状态转换为待发货
  • ④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。待发货 注意事项
1、避免把某个“程序动作”当作是一种“状态”来处理。那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
2、状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。
3、spring statemachine

3.1 状态机spring statemachine 概述

Spring Statemachine是应用程序开发人员在Spring应用程序中使用状态机概念的框架
Spring Statemachine旨在提供以下功能:

  • 易于使用的扁平单级状态机,用于简单的使用案例。
  • 分层状态机结构,以简化复杂的状态配置。
  • 状态机区域提供更复杂的状态配置。
  • 使用触发器,转换,警卫和操作。
  • 键入安全配置适配器。
  • 生成器模式,用于在Spring Application上下文之外使用的简单实例化通常用例的食谱
  • 基于Zookeeper的分布式状态机
  • 状态机事件监听器。
  • UML Eclipse Papyrus建模。
  • 将计算机配置存储在永久存储中。
  • Spring IOC集成将bean与状态机关联起来。
状态机功能强大,因为行为始终保证一致,使调试相对容易。这是因为操作规则是在机器启动时写成的。这个想法是你的应用程序可能存在于有限数量的状态中,某些预定义的触发器可以将你的应用程序从一个状态转移到另一个状态。此类触发器可以基于事件或计时器。
在应用程序之外定义高级逻辑然后依靠状态机来管理状态要容易得多。您可以通过发送事件,侦听更改或仅请求当前状态来与状态机进行交互。
官网:spring.io/projects/sp…
3.2 快速开始

Spring Boot 基础就不介绍了,推荐看这个实战项目:
https://github.com/javastacks/spring-boot-best-practice
以订单状态扭转的例子为例:
表结构设计如下:
  1. CREATE TABLE `tb_order` (
  2.       `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  3.       `order_code` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单编码',
  4.       `status` smallint(3) DEFAULT NULL COMMENT '订单状态',
  5.       `name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单名称',
  6.       `price` decimal(12,2) DEFAULT NULL COMMENT '价格',
  7.       `delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标记,0未删除  1已删除',
  8.       `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  9.       `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间',
  10.       `create_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',
  11.       `update_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',
  12.       `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
  13.       `remark` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
  14.       PRIMARY KEY (`id`)
  15.     ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';
  16.     /*Data for the table `tb_order` */
  17.     insert  into `tb_order`(`id`,`order_code`,`status`,`name`,`price`,`delete_flag`,`create_time`,`update_time`,`create_user_code`,`update_user_code`,`version`,`remark`) values
  18.     (2,'A111',1,'A','22.00',0,'2022-10-15 16:14:11','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
  19.     (3,'A111',1,'订单A','22.00',0,'2022-10-02 21:53:13','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
  20.     (4,'A111',1,'订单A','22.00',0,'2022-10-02 21:53:13','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
  21.     (5,'A111',1,'订单A','22.00',0,'2022-10-03 09:08:30','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL);
复制代码
1)引入依赖
  1.     <dependency>
  2.         <groupId>org.springframework.statemachine</groupId>
  3.         <artifactId>spring-statemachine-redis</artifactId>
  4.         <version>1.2.9.RELEASE</version>
  5.     </dependency>
  6.    
  7.     <dependency>
  8.         <groupId>org.springframework.statemachine</groupId>
  9.         <artifactId>spring-statemachine-starter</artifactId>
  10.         <version>2.0.1.RELEASE</version>
  11.     </dependency>
复制代码
2)定义状态机状态和事件

状态枚举:
  1. /**
  2. *
  3. */
  4. public enum OrderStatus {
  5.         // 待支付,待发货,待收货,已完成
  6.         WAIT_PAYMENT(1, "待支付"),
  7.         WAIT_DELIVER(2, "待发货"),
  8.         WAIT_RECEIVE(3, "待收货"),
  9.         FINISH(4, "已完成");
  10.         private Integer key;
  11.         private String desc;
  12.         OrderStatus(Integer key, String desc) {
  13.             this.key = key;
  14.             this.desc = desc;
  15.         }
  16.         public Integer getKey() {
  17.             return key;
  18.         }
  19.         public String getDesc() {
  20.             return desc;
  21.         }
  22.         public static OrderStatus getByKey(Integer key) {
  23.             for (OrderStatus e : values()) {
  24.                 if (e.getKey().equals(key)) {
  25.                     return e;
  26.                 }
  27.             }
  28.             throw new RuntimeException("enum not exists.");
  29.         }
  30.     }
复制代码
事件:
  1. /**
  2. *
  3. */
  4. public enum OrderStatusChangeEvent {
  5.         // 支付,发货,确认收货
  6.         PAYED, DELIVERY, RECEIVED;
  7. }
复制代码
3)定义状态机规则和配置状态机
  1. @Configuration
  2.     @EnableStateMachine(name = "orderStateMachine")
  3.     public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
  4.         /**
  5.          * 配置状态
  6.          *
  7.          * @param states
  8.          * @throws Exception
  9.          */
  10.         public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
  11.             states
  12.                     .withStates()
  13.                     .initial(OrderStatus.WAIT_PAYMENT)
  14.                     .states(EnumSet.allOf(OrderStatus.class));
  15.         }
  16.         /**
  17.          * 配置状态转换事件关系
  18.          *
  19.          * @param transitions
  20.          * @throws Exception
  21.          */
  22.         public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
  23.             transitions
  24.                     //支付事件:待支付-》待发货
  25.                     .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
  26.                     .and()
  27.                     //发货事件:待发货-》待收货
  28.                     .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
  29.                     .and()
  30.                     //收货事件:待收货-》已完成
  31.                     .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
  32.         }
  33.     }
复制代码
配置持久化:
  1. /**
  2. *
  3. */
  4.     @Configuration
  5.     @Slf4j
  6.     public class Persist<E, S> {
  7.         /**
  8.          * 持久化到内存map中
  9.          *
  10.          * @return
  11.          */
  12.         @Bean(name = "stateMachineMemPersister")
  13.         public static StateMachinePersister getPersister() {
  14.             return new DefaultStateMachinePersister(new StateMachinePersist() {
  15.                 @Override
  16.                 public void write(StateMachineContext context, Object contextObj) throws Exception {
  17.                     log.info("持久化状态机,context:{},contextObj:{}", JSON.toJSONString(context), JSON.toJSONString(contextObj));
  18.                     map.put(contextObj, context);
  19.                 }
  20.                 @Override
  21.                 public StateMachineContext read(Object contextObj) throws Exception {
  22.                     log.info("获取状态机,contextObj:{}", JSON.toJSONString(contextObj));
  23.                     StateMachineContext stateMachineContext = (StateMachineContext) map.get(contextObj);
  24.                     log.info("获取状态机结果,stateMachineContext:{}", JSON.toJSONString(stateMachineContext));
  25.                     return stateMachineContext;
  26.                 }
  27.                 private Map map = new HashMap();
  28.             });
  29.         }
  30.         @Resource
  31.         private RedisConnectionFactory redisConnectionFactory;
  32.         /**
  33.          * 持久化到redis中,在分布式系统中使用
  34.          *
  35.          * @return
  36.          */
  37.         @Bean(name = "stateMachineRedisPersister")
  38.         public RedisStateMachinePersister<E, S> getRedisPersister() {
  39.             RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
  40.             RepositoryStateMachinePersist p = new RepositoryStateMachinePersist<>(repository);
  41.             return new RedisStateMachinePersister<>(p);
  42.         }
  43.     }
复制代码
4)业务系统

controller:
  1. /**
  2. *
  3. */
  4.     @RestController
  5.     @RequestMapping("/order")
  6.     public class OrderController {
  7.         @Resource
  8.         private OrderService orderService;
  9.         /**
  10.          * 根据id查询订单
  11.          *
  12.          * @return
  13.          */
  14.         @RequestMapping("/getById")
  15.         public Order getById(@RequestParam("id") Long id) {
  16.             //根据id查询订单
  17.             Order order = orderService.getById(id);
  18.             return order;
  19.         }
  20.         /**
  21.          * 创建订单
  22.          *
  23.          * @return
  24.          */
  25.         @RequestMapping("/create")
  26.         public String create(@RequestBody Order order) {
  27.             //创建订单
  28.             orderService.create(order);
  29.             return "sucess";
  30.         }
  31.         /**
  32.          * 对订单进行支付
  33.          *
  34.          * @param id
  35.          * @return
  36.          */
  37.         @RequestMapping("/pay")
  38.         public String pay(@RequestParam("id") Long id) {
  39.             //对订单进行支付
  40.             orderService.pay(id);
  41.             return "success";
  42.         }
  43.         /**
  44.          * 对订单进行发货
  45.          *
  46.          * @param id
  47.          * @return
  48.          */
  49.         @RequestMapping("/deliver")
  50.         public String deliver(@RequestParam("id") Long id) {
  51.             //对订单进行确认收货
  52.             orderService.deliver(id);
  53.             return "success";
  54.         }
  55.         /**
  56.          * 对订单进行确认收货
  57.          *
  58.          * @param id
  59.          * @return
  60.          */
  61.         @RequestMapping("/receive")
  62.         public String receive(@RequestParam("id") Long id) {
  63.             //对订单进行确认收货
  64.             orderService.receive(id);
  65.             return "success";
  66.         }
  67.     }
复制代码
servie:
  1. /**
  2. *
  3. */
  4.     @Service("orderService")
  5.     @Slf4j
  6.     public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
  7.         @Resource
  8.         private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  9.         @Resource
  10.         private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
  11.         @Resource
  12.         private OrderMapper orderMapper;
  13.         /**
  14.          * 创建订单
  15.          *
  16.          * @param order
  17.          * @return
  18.          */
  19.         public Order create(Order order) {
  20.             order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
  21.             orderMapper.insert(order);
  22.             return order;
  23.         }
  24.         /**
  25.          * 对订单进行支付
  26.          *
  27.          * @param id
  28.          * @return
  29.          */
  30.         public Order pay(Long id) {
  31.             Order order = orderMapper.selectById(id);
  32.             log.info("线程名称:{},尝试支付,订单号:{}" ,Thread.currentThread().getName() , id);
  33.             if (!sendEvent(OrderStatusChangeEvent.PAYED, order)) {
  34.                 log.error("线程名称:{},支付失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
  35.                 throw new RuntimeException("支付失败, 订单状态异常");
  36.             }
  37.             return order;
  38.         }
  39.         /**
  40.          * 对订单进行发货
  41.          *
  42.          * @param id
  43.          * @return
  44.          */
  45.         public Order deliver(Long id) {
  46.             Order order = orderMapper.selectById(id);
  47.             log.info("线程名称:{},尝试发货,订单号:{}" ,Thread.currentThread().getName() , id);
  48.             if (!sendEvent(OrderStatusChangeEvent.DELIVERY, order)) {
  49.                 log.error("线程名称:{},发货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
  50.                 throw new RuntimeException("发货失败, 订单状态异常");
  51.             }
  52.             return order;
  53.         }
  54.         /**
  55.          * 对订单进行确认收货
  56.          *
  57.          * @param id
  58.          * @return
  59.          */
  60.         public Order receive(Long id) {
  61.             Order order = orderMapper.selectById(id);
  62.             log.info("线程名称:{},尝试收货,订单号:{}" ,Thread.currentThread().getName() , id);
  63.             if (!sendEvent(OrderStatusChangeEvent.RECEIVED, order)) {
  64.                 log.error("线程名称:{},收货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
  65.                 throw new RuntimeException("收货失败, 订单状态异常");
  66.             }
  67.             return order;
  68.         }
  69.         /**
  70.          * 发送订单状态转换事件
  71.          * synchronized修饰保证这个方法是线程安全的
  72.          *
  73.          * @param changeEvent
  74.          * @param order
  75.          * @return
  76.          */
  77.         private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
  78.             boolean result = false;
  79.             try {
  80.                 //启动状态机
  81.                 orderStateMachine.start();
  82.                 //尝试恢复状态机状态
  83.                 stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  84.                 Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  85.                 result = orderStateMachine.sendEvent(message);
  86.                 //持久化状态机状态
  87.                 stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  88.             } catch (Exception e) {
  89.                 log.error("订单操作失败:{}", e);
  90.             } finally {
  91.                 orderStateMachine.stop();
  92.             }
  93.             return result;
  94.         }
  95.     }
复制代码
监听状态的变化:
  1. /**
  2. *
  3. */
  4.     @Component("orderStateListener")
  5.     @WithStateMachine(name = "orderStateMachine")
  6.     @Slf4j
  7.     public class OrderStateListenerImpl {
  8.         @Resource
  9.         private OrderMapper orderMapper;
  10.         @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
  11.         public void payTransition(Message<OrderStatusChangeEvent> message) {
  12.             Order order = (Order) message.getHeaders().get("order");
  13.             log.info("支付,状态机反馈信息:{}",  message.getHeaders().toString());
  14.             //更新订单
  15.             order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
  16.             orderMapper.updateById(order);
  17.             //TODO 其他业务
  18.         }
  19.         @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
  20.         public void deliverTransition(Message<OrderStatusChangeEvent> message) {
  21.             Order order = (Order) message.getHeaders().get("order");
  22.             log.info("发货,状态机反馈信息:{}",  message.getHeaders().toString());
  23.             //更新订单
  24.             order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
  25.             orderMapper.updateById(order);
  26.             //TODO 其他业务
  27.         }
  28.         @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
  29.         public void receiveTransition(Message<OrderStatusChangeEvent> message) {
  30.             Order order = (Order) message.getHeaders().get("order");
  31.             log.info("确认收货,状态机反馈信息:{}",  message.getHeaders().toString());
  32.             //更新订单
  33.             order.setStatus(OrderStatus.FINISH.getKey());
  34.             orderMapper.updateById(order);
  35.             //TODO 其他业务
  36.         }
  37.     }
复制代码
3.3 测试验证

1)验证业务

正常流程结束。如果对一个订单进行支付了,再次进行支付,则会报错:http://localhost:8084/order/pay?id=2
报错如下:

2)验证持久化

内存
使用内存持久化类持久化:
  1. /**
  2. *
  3. */
  4. @Resource
  5.     private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
  6.     /**
  7.      * 发送订单状态转换事件
  8.      * synchronized修饰保证这个方法是线程安全的
  9.      *
  10.      * @param changeEvent
  11.      * @param order
  12.      * @return
  13.      */
  14.     private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
  15.         boolean result = false;
  16.         try {
  17.             //启动状态机
  18.             orderStateMachine.start();
  19.             //尝试恢复状态机状态
  20.             stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  21.             Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  22.             result = orderStateMachine.sendEvent(message);
  23.             //持久化状态机状态
  24.             stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  25.         } catch (Exception e) {
  26.             log.error("订单操作失败:{}", e);
  27.         } finally {
  28.             orderStateMachine.stop();
  29.         }
  30.         return result;
  31.     }
复制代码
redis持久化
引入依赖:
  1. <dependency>
  2.     <groupId>org.springframework.statemachine</groupId>
  3.     <artifactId>spring-statemachine-redis</artifactId>
  4.     <version>1.2.9.RELEASE</version>
  5. </dependency>
复制代码
配置yaml:
  1. spring:
  2.   redis:
  3.     database: 0
  4.     host: localhost
  5.     jedis:
  6.       pool:
  7.         max-active: 8
  8.         max-idle: 8
  9.         max-wait: ''
  10.         min-idle: 0
  11.     password: ''
  12.     port: 6379
  13.     timeout: 0
复制代码
使用redis持久化类持久化:
  1. /**
  2. *
  3. */
  4. @Resource
  5.     private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineRedisPersister;
  6.     /**
  7.      * 发送订单状态转换事件
  8.      * synchronized修饰保证这个方法是线程安全的
  9.      *
  10.      * @param changeEvent
  11.      * @param order
  12.      * @return
  13.      */
  14.     private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
  15.         boolean result = false;
  16.         try {
  17.             //启动状态机
  18.             orderStateMachine.start();
  19.             //尝试恢复状态机状态
  20.             stateMachineRedisPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  21.             Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  22.             result = orderStateMachine.sendEvent(message);
  23.             //持久化状态机状态
  24.             stateMachineRedisPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  25.         } catch (Exception e) {
  26.             log.error("订单操作失败:{}", e);
  27.         } finally {
  28.             orderStateMachine.stop();
  29.         }
  30.         return result;
  31.     }
复制代码
3.4 状态机存在的问题

1)stateMachine无法抛出异常,异常会被状态机给消化掉

问题现象
从orderStateMachine.sendEvent(message);获取的结果无法感知到。无论执行正常还是抛出异常,都返回true。
  1. @Resource
  2.     private OrderMapper orderMapper;
  3.     @Resource
  4.     private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  5.     @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
  6.     @Transactional(rollbackFor = Exception.class)
  7.     public void payTransition(Message<OrderStatusChangeEvent> message) {
  8.         Order order = (Order) message.getHeaders().get("order");
  9.         log.info("支付,状态机反馈信息:{}",  message.getHeaders().toString());
  10.         try {
  11.             //更新订单
  12.             order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
  13.             orderMapper.updateById(order);
  14.             //TODO 其他业务
  15.             //模拟异常
  16.             if(Objects.equals(order.getName(),"A")){
  17.                 throw new RuntimeException("执行业务异常");
  18.             }
  19.         } catch (Exception e) {
  20.             //如果出现异常,记录异常信息,抛出异常信息进行回滚
  21.             log.error("payTransition 出现异常:{}",e);
  22.             throw e;
  23.         }
  24.     }
复制代码
监听事件抛出异常,在发送事件中无法感知:
  1. private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
  2.         boolean result = false;
  3.         try {
  4.             //启动状态机
  5.             orderStateMachine.start();
  6.             //尝试恢复状态机状态
  7.             stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  8.             Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  9.              //事件执行异常了,依然返回true,无法感知异常
  10.             result = orderStateMachine.sendEvent(message);
  11.             if(result){
  12.                 //持久化状态机状态,如果根据true持久化,则会出现问题
  13.                 stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  14.             }
  15.         } catch (Exception e) {
  16.             log.error("订单操作失败:{}", e);
  17.         } finally {
  18.             orderStateMachine.stop();
  19.         }
  20.         return result;
  21.     }
复制代码
调试发现:发送事件和监听事件是一个线程,发送事件的结果是在监听操作执行完之后才返回

监听线程:

解决方案:自己保存异常到数据库或者内存中,进行判断
也可以通过接口:org.springframework.statemachine.StateMachine##getExtendedState
方法把执行状态放入这个变量中
  1. public interface ExtendedState {
  2.         Map<Object, Object> getVariables();
  3.         <T> T get(Object var1, Class<T> var2);
  4.         void setExtendedStateChangeListener(ExtendedState.ExtendedStateChangeListener var1);
  5.         public interface ExtendedStateChangeListener {
  6.             void changed(Object var1, Object var2);
  7.         }
  8.     }
复制代码
org.springframework.statemachine.support.DefaultExtendedState##getVariables
  1. private final Map<Object, Object> variables;
  2.     public DefaultExtendedState() {
  3.         this.variables = new ObservableMap(new ConcurrentHashMap(), new DefaultExtendedState.LocalMapChangeListener());
  4.     }
  5.     public Map<Object, Object> getVariables() {
  6.         return this.variables;
  7.     }
复制代码
改造监听状态:把业务的执行结果进行保存,1成功,0失败
  1.     @Resource
  2.     private OrderMapper orderMapper;
  3.     @Resource
  4.     private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  5.     @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
  6.     @Transactional(rollbackFor = Exception.class)
  7.     public void payTransition(Message<OrderStatusChangeEvent> message) {
  8.         Order order = (Order) message.getHeaders().get("order");
  9.         log.info("支付,状态机反馈信息:{}",  message.getHeaders().toString());
  10.         try {
  11.             //更新订单
  12.             order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
  13.             orderMapper.updateById(order);
  14.             //TODO 其他业务
  15.             //模拟异常
  16.             if(Objects.equals(order.getName(),"A")){
  17.                 throw new RuntimeException("执行业务异常");
  18.             }
  19.             //成功 则为1
  20.             orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
  21.         } catch (Exception e) {
  22.             //如果出现异常,则进行回滚
  23.             log.error("payTransition 出现异常:{}",e);
  24.             //将异常信息变量信息中,失败则为0
  25.             orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(), 0);
  26.             throw e;
  27.         }
  28.     }
复制代码
发送事件改造:如果获取到业务执行异常,则返回失败,不进行状态机持久化 com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##sendEvent
  1. @Resource
  2.     private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  3.     @Resource
  4.     private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
  5.     /**
  6.      * 发送订单状态转换事件
  7.      * synchronized修饰保证这个方法是线程安全的
  8.      *
  9.      * @param changeEvent
  10.      * @param order
  11.      * @return
  12.      */
  13.     private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order){
  14.         boolean result = false;
  15.         try {
  16.             //启动状态机
  17.             orderStateMachine.start();
  18.             //尝试恢复状态机状态
  19.             stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  20.             Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  21.             result = orderStateMachine.sendEvent(message);
  22.             if(!result){
  23.                 return false;
  24.             }
  25.             //获取到监听的结果信息
  26.             Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition + order.getId());
  27.             //操作完成之后,删除本次对应的key信息
  28.             orderStateMachine.getExtendedState().getVariables().remove(CommonConstants.payTransition+order.getId());
  29.             //如果事务执行成功,则持久化状态机
  30.             if(Objects.equals(1,Integer.valueOf(o))){
  31.                 //持久化状态机状态
  32.                 stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  33.             }else {
  34.                 //订单执行业务异常
  35.                 return false;
  36.             }
  37.         } catch (Exception e) {
  38.             log.error("订单操作失败:{}", e);
  39.         } finally {
  40.             orderStateMachine.stop();
  41.         }
  42.         return result;
  43.     }
复制代码
代码优化

  • 发送事件只针对了支付,如果是非支付事件呢?
  1. //获取到监听的结果信息
  2. Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition + order.getId());
复制代码

  • 监听设置状态的代码有重复代码,需要进行优化,可使用aop
  1. try {
  2.         //TODO 其他业务
  3.         //成功 则为1
  4.         orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
  5.     } catch (Exception e) {
  6.         //如果出现异常,则进行回滚
  7.         log.error("payTransition 出现异常:{}",e);
  8.         //将异常信息变量信息中,失败则为0
  9.         orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(), 0);
  10.         throw e;
  11.     }
复制代码
常量类:
  1. public interface CommonConstants {
  2.         String orderHeader="order";
  3.         String payTransition="payTransition";
  4.         String deliverTransition="deliverTransition";
  5.         String receiveTransition="receiveTransition";
  6.     }
复制代码
支付发送事件:com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##pay
  1. @Resource
  2.     private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  3.     @Resource
  4.     private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
  5.     @Resource
  6.     private OrderMapper orderMapper;
  7.     /**
  8.      * 对订单进行支付
  9.      *
  10.      * @param id
  11.      * @return
  12.      */
  13.     public Order pay(Long id) {
  14.         Order order = orderMapper.selectById(id);
  15.         log.info("线程名称:{},尝试支付,订单号:{}" ,Thread.currentThread().getName() , id);
  16.         if (!sendEvent(OrderStatusChangeEvent.PAYED, order,CommonConstants.payTransition)) {
  17.             log.error("线程名称:{},支付失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
  18.             throw new RuntimeException("支付失败, 订单状态异常");
  19.         }
  20.         return order;
  21.     }
  22.     /**
  23.      * 发送订单状态转换事件
  24.      * synchronized修饰保证这个方法是线程安全的
  25.      *
  26.      * @param changeEvent
  27.      * @param order
  28.      * @return
  29.      */
  30.     private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order,String key){
  31.         boolean result = false;
  32.         try {
  33.             //启动状态机
  34.             orderStateMachine.start();
  35.             //尝试恢复状态机状态
  36.             stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
  37.             Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
  38.             result = orderStateMachine.sendEvent(message);
  39.             if(!result){
  40.                 return false;
  41.             }
  42.             //获取到监听的结果信息
  43.             Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(key + order.getId());
  44.             //操作完成之后,删除本次对应的key信息
  45.             orderStateMachine.getExtendedState().getVariables().remove(key+order.getId());
  46.             //如果事务执行成功,则持久化状态机
  47.             if(Objects.equals(1,Integer.valueOf(o))){
  48.                 //持久化状态机状态
  49.                 stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
  50.             }else {
  51.                 //订单执行业务异常
  52.                 return false;
  53.             }
  54.         } catch (Exception e) {
  55.             log.error("订单操作失败:{}", e);
  56.         } finally {
  57.             orderStateMachine.stop();
  58.         }
  59.         return result;
  60.     }
复制代码
使用aop对监听事件切面,把业务执行结果封装到状态机的变量中,注解:
  1. @Retention(RetentionPolicy.RUNTIME)
  2.     public @interface LogResult {
  3.         /**
  4.          *执行的业务key
  5.          *
  6.          * @return String
  7.          */
  8.         String key();
  9.     }
复制代码
切面:
  1. @Component
  2.     @Aspect
  3.     @Slf4j
  4.     public class LogResultAspect {
  5.         //拦截 LogHistory注解
  6.         @Pointcut("@annotation(com.zengqingfa.springboot.state.demo.aop.annotation.LogResult)")
  7.         private void logResultPointCut() {
  8.             //logResultPointCut 日志注解切点
  9.         }
  10.         @Resource
  11.         private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
  12.         @Around("logResultPointCut()")
  13.         public Object logResultAround(ProceedingJoinPoint pjp) throws Throwable {
  14.             //获取参数
  15.             Object[] args = pjp.getArgs();
  16.             log.info("参数args:{}", args);
  17.             Message message = (Message) args[0];
  18.             Order order = (Order) message.getHeaders().get("order");
  19.             //获取方法
  20.             Method method = ((MethodSignature) pjp.getSignature()).getMethod();
  21.             // 获取LogHistory注解
  22.             LogResult logResult = method.getAnnotation(LogResult.class);
  23.             String key = logResult.key();
  24.             Object returnVal = null;
  25.             try {
  26.                 //执行方法
  27.                 returnVal = pjp.proceed();
  28.                 //如果业务执行正常,则保存信息
  29.                 //成功 则为1
  30.                 orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 1);
  31.             } catch (Throwable e) {
  32.                 log.error("e:{}", e.getMessage());
  33.                 //如果业务执行异常,则保存信息
  34.                 //将异常信息变量信息中,失败则为0
  35.                 orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 0);
  36.                 throw e;
  37.             }
  38.             return returnVal;
  39.         }
  40.     }
复制代码
监听类使用注解:
  1. @Component("orderStateListener")
  2.     @WithStateMachine(name = "orderStateMachine")
  3.     @Slf4j
  4.     public class OrderStateListenerImpl {
  5.         @Resource
  6.         private OrderMapper orderMapper;
  7.         @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
  8.         @Transactional(rollbackFor = Exception.class)
  9.         @LogResult(key = CommonConstants.payTransition)
  10.         public void payTransition(Message<OrderStatusChangeEvent> message) {
  11.             Order order = (Order) message.getHeaders().get("order");
  12.             log.info("支付,状态机反馈信息:{}", message.getHeaders().toString());
  13.             //更新订单
  14.             order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
  15.             orderMapper.updateById(order);
  16.             //TODO 其他业务
  17.             //模拟异常
  18.             if (Objects.equals(order.getName(), "A")) {
  19.                 throw new RuntimeException("执行业务异常");
  20.             }
  21.         }
  22.         @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
  23.         @LogResult(key = CommonConstants.deliverTransition)
  24.         public void deliverTransition(Message<OrderStatusChangeEvent> message) {
  25.             Order order = (Order) message.getHeaders().get("order");
  26.             log.info("发货,状态机反馈信息:{}", message.getHeaders().toString());
  27.             //更新订单
  28.             order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
  29.             orderMapper.updateById(order);
  30.             //TODO 其他业务
  31.         }
  32.         @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
  33.         @LogResult(key = CommonConstants.receiveTransition)
  34.         public void receiveTransition(Message<OrderStatusChangeEvent> message) {
  35.             Order order = (Order) message.getHeaders().get("order");
  36.             log.info("确认收货,状态机反馈信息:{}", message.getHeaders().toString());
  37.             //更新订单
  38.             order.setStatus(OrderStatus.FINISH.getKey());
  39.             orderMapper.updateById(order);
  40.             //TODO 其他业务
  41.         }
  42.     }
复制代码
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

来自云龙湖轮廓分明的月亮

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

标签云

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