主頁 > 後端開發 > 專案終于用上了 Spring 狀態機,非常優雅!

專案終于用上了 Spring 狀態機,非常優雅!

2023-05-26 08:22:11 後端開發

來源: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旨在提供以下功能:

  1. 易于使用的扁平單級狀態機,用于簡單的使用案例,
  2. 分層狀態機結構,以簡化復雜的狀態配置,
  3. 狀態機區域提供更復雜的狀態配置,
  4. 使用觸發器,轉換,警衛和操作,
  5. 鍵入安全配置配接器,
  6. 生成器模式,用于在Spring Application背景關系之外使用的簡單實體化通常用例的食譜
  7. 基于Zookeeper的分布式狀態機
  8. 狀態機事件監聽器,
  9. UML Eclipse Papyrus建模,
  10. 將計算機配置存盤在永久存盤中,
  11. Spring IOC集成將bean與狀態機關聯起來,

狀態機功能強大,因為行為始終保證一致,使除錯相對容易,這是因為操作規則是在機器啟動時寫成的,這個想法是你的應用程式可能存在于有限數量的狀態中,某些預定義的觸發器可以將你的應用程式從一個狀態轉移到另一個狀態,此類觸發器可以基于事件或計時器,

在應用程式之外定義高級邏輯然后依靠狀態機來管理狀態要容易得多,您可以通過發送事件,偵聽更改或僅請求當前狀態來與狀態機進行互動,

官網:spring.io/projects/sp…

3.2 快速開始

Spring Boot 基礎就不介紹了,推薦看這個實戰專案:

https://github.com/javastacks/spring-boot-best-practice

以訂單狀態扭轉的例子為例:

表結構設計如下:

CREATE TABLE `tb_order` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
      `order_code` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '訂單編碼',
      `status` smallint(3) DEFAULT NULL COMMENT '訂單狀態',
      `name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '訂單名稱',
      `price` decimal(12,2) DEFAULT NULL COMMENT '價格',
      `delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '洗掉標記,0未洗掉  1已洗掉',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創建時間',
      `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新時間',
      `create_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '創建人',
      `update_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',
      `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本號',
      `remark` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '備注',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='訂單表';

    /*Data for the table `tb_order` */

    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
    (2,'A111',1,'A','22.00',0,'2022-10-15 16:14:11','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
    (3,'A111',1,'訂單A','22.00',0,'2022-10-02 21:53:13','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
    (4,'A111',1,'訂單A','22.00',0,'2022-10-02 21:53:13','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL),
    (5,'A111',1,'訂單A','22.00',0,'2022-10-03 09:08:30','2022-10-02 21:29:14','zhangsan','zhangsan',0,NULL);
1)引入依賴
 <!-- redis持久化狀態機 -->
    <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-redis</artifactId>
        <version>1.2.9.RELEASE</version>
    </dependency>
    <!--狀態機-->
    <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-starter</artifactId>
        <version>2.0.1.RELEASE</version>
    </dependency>
2)定義狀態機狀態和事件

狀態列舉:

/**
* 
*/
public enum OrderStatus {
        // 待支付,待發貨,待識訓,已完成
        WAIT_PAYMENT(1, "待支付"),
        WAIT_DELIVER(2, "待發貨"),
        WAIT_RECEIVE(3, "待識訓"),
        FINISH(4, "已完成");
        private Integer key;
        private String desc;
        OrderStatus(Integer key, String desc) {
            this.key = key;
            this.desc = desc;
        }
        public Integer getKey() {
            return key;
        }
        public String getDesc() {
            return desc;
        }
        public static OrderStatus getByKey(Integer key) {
            for (OrderStatus e : values()) {
                if (e.getKey().equals(key)) {
                    return e;
                }
            }
            throw new RuntimeException("enum not exists.");
        }
    }

事件:

/**
* 
*/
public enum OrderStatusChangeEvent {
        // 支付,發貨,確認識訓
        PAYED, DELIVERY, RECEIVED;
}
3)定義狀態機規則和配置狀態機
 @Configuration
    @EnableStateMachine(name = "orderStateMachine")
    public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
        /**
         * 配置狀態
         *
         * @param states
         * @throws Exception
         */
        public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
            states
                    .withStates()
                    .initial(OrderStatus.WAIT_PAYMENT)
                    .states(EnumSet.allOf(OrderStatus.class));
        }
        /**
         * 配置狀態轉換事件關系
         *
         * @param transitions
         * @throws Exception
         */
        public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
            transitions
                    //支付事件:待支付-》待發貨
                    .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                    .and()
                    //發貨事件:待發貨-》待識訓
                    .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                    .and()
                    //識訓事件:待識訓-》已完成
                    .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
        }
    }

配置持久化:

 /**
 * 
 */
    @Configuration
    @Slf4j
    public class Persist<E, S> {
        /**
         * 持久化到記憶體map中
         *
         * @return
         */
        @Bean(name = "stateMachineMemPersister")
        public static StateMachinePersister getPersister() {
            return new DefaultStateMachinePersister(new StateMachinePersist() {
                @Override
                public void write(StateMachineContext context, Object contextObj) throws Exception {
                    log.info("持久化狀態機,context:{},contextObj:{}", JSON.toJSONString(context), JSON.toJSONString(contextObj));
                    map.put(contextObj, context);
                }
                @Override
                public StateMachineContext read(Object contextObj) throws Exception {
                    log.info("獲取狀態機,contextObj:{}", JSON.toJSONString(contextObj));
                    StateMachineContext stateMachineContext = (StateMachineContext) map.get(contextObj);
                    log.info("獲取狀態機結果,stateMachineContext:{}", JSON.toJSONString(stateMachineContext));
                    return stateMachineContext;
                }
                private Map map = new HashMap();
            });
        }

        @Resource
        private RedisConnectionFactory redisConnectionFactory;
        /**
         * 持久化到redis中,在分布式系統中使用
         *
         * @return
         */
        @Bean(name = "stateMachineRedisPersister")
        public RedisStateMachinePersister<E, S> getRedisPersister() {
            RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
            RepositoryStateMachinePersist p = new RepositoryStateMachinePersist<>(repository);
            return new RedisStateMachinePersister<>(p);
        }
    }
4)業務系統

controller:

 /**
 * 
 */
    @RestController
    @RequestMapping("/order")
    public class OrderController {
        @Resource
        private OrderService orderService;
        /**
         * 根據id查詢訂單
         *
         * @return
         */
        @RequestMapping("/getById")
        public Order getById(@RequestParam("id") Long id) {
            //根據id查詢訂單
            Order order = orderService.getById(id);
            return order;
        }
        /**
         * 創建訂單
         *
         * @return
         */
        @RequestMapping("/create")
        public String create(@RequestBody Order order) {
            //創建訂單
            orderService.create(order);
            return "sucess";
        }
        /**
         * 對訂單進行支付
         *
         * @param id
         * @return
         */
        @RequestMapping("/pay")
        public String pay(@RequestParam("id") Long id) {
            //對訂單進行支付
            orderService.pay(id);
            return "success";
        }

        /**
         * 對訂單進行發貨
         *
         * @param id
         * @return
         */
        @RequestMapping("/deliver")
        public String deliver(@RequestParam("id") Long id) {
            //對訂單進行確認識訓
            orderService.deliver(id);
            return "success";
        }
        /**
         * 對訂單進行確認識訓
         *
         * @param id
         * @return
         */
        @RequestMapping("/receive")
        public String receive(@RequestParam("id") Long id) {
            //對訂單進行確認識訓
            orderService.receive(id);
            return "success";
        }
    }

servie:

 /**
 * 
 */
    @Service("orderService")
    @Slf4j
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
        @Resource
        private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
        @Resource
        private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
        @Resource
        private OrderMapper orderMapper;
        /**
         * 創建訂單
         *
         * @param order
         * @return
         */
        public Order create(Order order) {
            order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
            orderMapper.insert(order);
            return order;
        }
        /**
         * 對訂單進行支付
         *
         * @param id
         * @return
         */
        public Order pay(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("執行緒名稱:{},嘗試支付,訂單號:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.PAYED, order)) {
                log.error("執行緒名稱:{},支付失敗, 狀態例外,訂單資訊:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("支付失敗, 訂單狀態例外");
            }
            return order;
        }
        /**
         * 對訂單進行發貨
         *
         * @param id
         * @return
         */
        public Order deliver(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("執行緒名稱:{},嘗試發貨,訂單號:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.DELIVERY, order)) {
                log.error("執行緒名稱:{},發貨失敗, 狀態例外,訂單資訊:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("發貨失敗, 訂單狀態例外");
            }
            return order;
        }
        /**
         * 對訂單進行確認識訓
         *
         * @param id
         * @return
         */
        public Order receive(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("執行緒名稱:{},嘗試識訓,訂單號:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.RECEIVED, order)) {
                log.error("執行緒名稱:{},識訓失敗, 狀態例外,訂單資訊:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("識訓失敗, 訂單狀態例外");
            }
            return order;
        }
        /**
         * 發送訂單狀態轉換事件
         * synchronized修飾保證這個方法是執行緒安全的
         *
         * @param changeEvent
         * @param order
         * @return
         */
        private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
            boolean result = false;
            try {
                //啟動狀態機
                orderStateMachine.start();
                //嘗試恢復狀態機狀態
                stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
                Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
                result = orderStateMachine.sendEvent(message);
                //持久化狀態機狀態
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            } catch (Exception e) {
                log.error("訂單操作失敗:{}", e);
            } finally {
                orderStateMachine.stop();
            }
            return result;
        }
    }

監聽狀態的變化:

 /**
 * 
 */
    @Component("orderStateListener")
    @WithStateMachine(name = "orderStateMachine")
    @Slf4j
    public class OrderStateListenerImpl {
        @Resource
        private OrderMapper orderMapper;

        @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
        public void payTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("支付,狀態機反饋資訊:{}",  message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
        }
        @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
        public void deliverTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("發貨,狀態機反饋資訊:{}",  message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
        }
        @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
        public void receiveTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("確認識訓,狀態機反饋資訊:{}",  message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.FINISH.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
        }
    }

3.3 測驗驗證

1)驗證業務
  • 新增一個訂單

    http://localhost:8084/order/create

  • 對訂單進行支付

    http://localhost:8084/order/pay?id=2

  • 對訂單進行發貨

    http://localhost:8084/order/deliver?id=2

  • 對訂單進行確認識訓

    http://localhost:8084/order/receive?id=2

正常流程結束,如果對一個訂單進行支付了,再次進行支付,則會報錯:http://localhost:8084/order/pay?id=2

報錯如下:

2)驗證持久化

記憶體

使用記憶體持久化類持久化:

 /**
 * 
 */
 @Resource
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;

    /**
     * 發送訂單狀態轉換事件
     * synchronized修飾保證這個方法是執行緒安全的
     *
     * @param changeEvent
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            //嘗試恢復狀態機狀態
            stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
            result = orderStateMachine.sendEvent(message);
            //持久化狀態機狀態
            stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
        } catch (Exception e) {
            log.error("訂單操作失敗:{}", e);
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

redis持久化

引入依賴:

<!-- redis持久化狀態機 -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-redis</artifactId>
    <version>1.2.9.RELEASE</version>
</dependency>

配置yaml:

spring:
  redis:
    database: 0
    host: localhost
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: ''
        min-idle: 0
    password: ''
    port: 6379
    timeout: 0

使用redis持久化類持久化:

 /**
 * 
 */
 @Resource
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineRedisPersister;

    /**
     * 發送訂單狀態轉換事件
     * synchronized修飾保證這個方法是執行緒安全的
     *
     * @param changeEvent
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            //嘗試恢復狀態機狀態
            stateMachineRedisPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
            result = orderStateMachine.sendEvent(message);
            //持久化狀態機狀態
            stateMachineRedisPersister.persist(orderStateMachine, String.valueOf(order.getId()));
        } catch (Exception e) {
            log.error("訂單操作失敗:{}", e);
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

3.4 狀態機存在的問題

1)stateMachine無法拋出例外,例外會被狀態機給消化掉

問題現象

從orderStateMachine.sendEvent(message);獲取的結果無法感知到,無論執行正常還是拋出例外,都回傳true,

 @Resource
    private OrderMapper orderMapper;

    @Resource
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;

    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    @Transactional(rollbackFor = Exception.class)
    public void payTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("支付,狀態機反饋資訊:{}",  message.getHeaders().toString());
        try {
            //更新訂單
            order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
            //模擬例外
            if(Objects.equals(order.getName(),"A")){
                throw new RuntimeException("執行業務例外");
            }
        } catch (Exception e) {
            //如果出現例外,記錄例外資訊,拋出例外資訊進行回滾
            log.error("payTransition 出現例外:{}",e);
            throw e;
        }
    }

監聽事件拋出例外,在發送事件中無法感知:

 private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            //嘗試恢復狀態機狀態
            stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
             //事件執行例外了,依然回傳true,無法感知例外
            result = orderStateMachine.sendEvent(message);
            if(result){
                //持久化狀態機狀態,如果根據true持久化,則會出現問題
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            }
        } catch (Exception e) {
            log.error("訂單操作失敗:{}", e);
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

除錯發現:發送事件和監聽事件是一個執行緒,發送事件的結果是在監聽操作執行完之后才回傳

監聽執行緒:

解決方案:自己保存例外到資料庫或者記憶體中,進行判斷

也可以通過介面:org.springframework.statemachine.StateMachine##getExtendedState

方法把執行狀態放入這個變數中

public interface ExtendedState {
        Map<Object, Object> getVariables();
        <T> T get(Object var1, Class<T> var2);
        void setExtendedStateChangeListener(ExtendedState.ExtendedStateChangeListener var1);
        public interface ExtendedStateChangeListener {
            void changed(Object var1, Object var2);
        }
    }

org.springframework.statemachine.support.DefaultExtendedState##getVariables

private final Map<Object, Object> variables;

    public DefaultExtendedState() {
        this.variables = new ObservableMap(new ConcurrentHashMap(), new DefaultExtendedState.LocalMapChangeListener());
    }

    public Map<Object, Object> getVariables() {
        return this.variables;
    }

改造監聽狀態:把業務的執行結果進行保存,1成功,0失敗

    @Resource
    private OrderMapper orderMapper;
    @Resource
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;

    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    @Transactional(rollbackFor = Exception.class)
    public void payTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("支付,狀態機反饋資訊:{}",  message.getHeaders().toString());
        try {
            //更新訂單
            order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
            //模擬例外
            if(Objects.equals(order.getName(),"A")){
                throw new RuntimeException("執行業務例外");
            }
            //成功 則為1
            orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
        } catch (Exception e) {
            //如果出現例外,則進行回滾
            log.error("payTransition 出現例外:{}",e);
            //將例外資訊變數資訊中,失敗則為0
            orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(), 0);
            throw e;
        }
    }

發送事件改造:如果獲取到業務執行例外,則回傳失敗,不進行狀態機持久化 com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##sendEvent

 @Resource
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
    @Resource
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;

    /**
     * 發送訂單狀態轉換事件
     * synchronized修飾保證這個方法是執行緒安全的
     *
     * @param changeEvent
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order){
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            //嘗試恢復狀態機狀態
            stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
            result = orderStateMachine.sendEvent(message);
            if(!result){
                return false;
            }
            //獲取到監聽的結果資訊
            Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition + order.getId());
            //操作完成之后,洗掉本次對應的key資訊
            orderStateMachine.getExtendedState().getVariables().remove(CommonConstants.payTransition+order.getId());
            //如果事務執行成功,則持久化狀態機
            if(Objects.equals(1,Integer.valueOf(o))){
                //持久化狀態機狀態
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            }else {
                //訂單執行業務例外
                return false;
            }
        } catch (Exception e) {
            log.error("訂單操作失敗:{}", e);
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

代碼優化

  • 發送事件只針對了支付,如果是非支付事件呢?
//獲取到監聽的結果資訊
Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition + order.getId());
  • 監聽設定狀態的代碼有重復代碼,需要進行優化,可使用aop
try {
        //TODO 其他業務
        //成功 則為1
        orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
    } catch (Exception e) {
        //如果出現例外,則進行回滾
        log.error("payTransition 出現例外:{}",e);
        //將例外資訊變數資訊中,失敗則為0
        orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(), 0);
        throw e;
    }

常量類:

public interface CommonConstants {
        String orderHeader="order";
        String payTransition="payTransition";
        String deliverTransition="deliverTransition";
        String receiveTransition="receiveTransition";
    }

支付發送事件:com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##pay

 @Resource
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
    @Resource
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
    @Resource
    private OrderMapper orderMapper;

    /**
     * 對訂單進行支付
     *
     * @param id
     * @return
     */
    public Order pay(Long id) {
        Order order = orderMapper.selectById(id);
        log.info("執行緒名稱:{},嘗試支付,訂單號:{}" ,Thread.currentThread().getName() , id);
        if (!sendEvent(OrderStatusChangeEvent.PAYED, order,CommonConstants.payTransition)) {
            log.error("執行緒名稱:{},支付失敗, 狀態例外,訂單資訊:{}", Thread.currentThread().getName(), order);
            throw new RuntimeException("支付失敗, 訂單狀態例外");
        }
        return order;
    }

    /**
     * 發送訂單狀態轉換事件
     * synchronized修飾保證這個方法是執行緒安全的
     *
     * @param changeEvent
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order,String key){
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            //嘗試恢復狀態機狀態
            stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
            result = orderStateMachine.sendEvent(message);
            if(!result){
                return false;
            }
            //獲取到監聽的結果資訊
            Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(key + order.getId());
            //操作完成之后,洗掉本次對應的key資訊
            orderStateMachine.getExtendedState().getVariables().remove(key+order.getId());
            //如果事務執行成功,則持久化狀態機
            if(Objects.equals(1,Integer.valueOf(o))){
                //持久化狀態機狀態
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            }else {
                //訂單執行業務例外
                return false;
            }
        } catch (Exception e) {
            log.error("訂單操作失敗:{}", e);
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

使用aop對監聽事件切面,把業務執行結果封裝到狀態機的變數中,注解:

 @Retention(RetentionPolicy.RUNTIME)
    public @interface LogResult {
        /**
         *執行的業務key
         *
         * @return String
         */
        String key();
    }

切面:

 @Component
    @Aspect
    @Slf4j
    public class LogResultAspect {

        //攔截 LogHistory注解
        @Pointcut("@annotation(com.zengqingfa.springboot.state.demo.aop.annotation.LogResult)")
        private void logResultPointCut() {
            //logResultPointCut 日志注解切點
        }
        @Resource
        private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;

        @Around("logResultPointCut()")
        public Object logResultAround(ProceedingJoinPoint pjp) throws Throwable {
            //獲取引數
            Object[] args = pjp.getArgs();
            log.info("引數args:{}", args);
            Message message = (Message) args[0];
            Order order = (Order) message.getHeaders().get("order");
            //獲取方法
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();
            // 獲取LogHistory注解
            LogResult logResult = method.getAnnotation(LogResult.class);
            String key = logResult.key();
            Object returnVal = null;
            try {
                //執行方法
                returnVal = pjp.proceed();
                //如果業務執行正常,則保存資訊
                //成功 則為1
                orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 1);
            } catch (Throwable e) {
                log.error("e:{}", e.getMessage());
                //如果業務執行例外,則保存資訊
                //將例外資訊變數資訊中,失敗則為0
                orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 0);
                throw e;
            }
            return returnVal;
        }
    }

監聽類使用注解:

 @Component("orderStateListener")
    @WithStateMachine(name = "orderStateMachine")
    @Slf4j
    public class OrderStateListenerImpl {
        @Resource
        private OrderMapper orderMapper;

        @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
        @Transactional(rollbackFor = Exception.class)
        @LogResult(key = CommonConstants.payTransition)
        public void payTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("支付,狀態機反饋資訊:{}", message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
            //模擬例外
            if (Objects.equals(order.getName(), "A")) {
                throw new RuntimeException("執行業務例外");
            }
        }
        @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
        @LogResult(key = CommonConstants.deliverTransition)
        public void deliverTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("發貨,狀態機反饋資訊:{}", message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
        }
        @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
        @LogResult(key = CommonConstants.receiveTransition)
        public void receiveTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("確認識訓,狀態機反饋資訊:{}", message.getHeaders().toString());
            //更新訂單
            order.setStatus(OrderStatus.FINISH.getKey());
            orderMapper.updateById(order);
            //TODO 其他業務
        }
    }

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了,,,

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553414.html

標籤:其他

上一篇:Kotlin難點

下一篇:返回列表

標籤雲
其他(159702) Python(38169) JavaScript(25452) Java(18129) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7211) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5341) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1944) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 專案終于用上了 Spring 狀態機,非常優雅!

    來源:https://www.duidaima.com/Group/Topic/JAVA/11942 ## **1、什么是狀態機** ### 1.1 什么是狀態 先來解釋什么是“狀態”( State )。現實事物是有不同狀態的,例如一個自動門,就有 open 和 closed 兩種狀態。我們通常所說 ......

    uj5u.com 2023-05-26 08:22:11 more
  • Kotlin難點

    [toc] # 高階函式 高階函式是將函式用作引數或回傳值的函式,還可以把函式賦值給一個變數。 所有函式型別都有一個圓括號括起來的引數型別串列以及一個回傳型別:(A, B) -> C 表示接受型別分別為 A 與 B 兩個引數并回傳一個 C 型別值的函式型別。 引數型別串列可以為空,如 () -> A ......

    uj5u.com 2023-05-26 08:16:54 more
  • 詳細解讀Java中Map集合的底層原理(干貨+原始碼解讀)

    本文將為大家詳細講解Java中的Map集合,這是我們進行開發時經常用到的知識點,也是大家在學習Java中很重要的一個知識點,更是我們在面試時有可能會問到的問題。文章較長,干貨滿滿,建議大家收藏慢慢學習。文末有本文重點總結,主頁有全系列文章分享。技術類問題,歡迎大家和我們一起交流討論! ......

    uj5u.com 2023-05-26 08:11:48 more
  • Python集合 (set) 的增刪改查及 copy()方法

    集合是無序的,不重復的資料集合,它里面的元素是可哈希的(不可變型別),但是集合本身是不可哈希(所以集合做不了字典的鍵)的。 以下是集合最重要的兩點: 1、去重,把一個串列變成集合,就自動去重了。 2、關系測驗,測驗兩組資料之前的交集、差集、并集等關系。 ### 一、集合的創建 ```python s ......

    uj5u.com 2023-05-26 08:11:43 more
  • 【編程日記】搭建PyCharm集成開發環境

    # 0.相關確定 本教程使用的版本號為專業版PyCharm 2022.3.2,如果您是初學者,為了更好的學習本教程,避免不必要的麻煩,請您下載使用與本教程一致的版本號。 # 1.PyCharm的下載 官網下載:https://www.jetbrains.com/pycharm/download/ot ......

    uj5u.com 2023-05-26 08:06:24 more
  • 如何證明Servlet是單例的?

    Servlet是web體系里面最重要的部分,下面羅列幾道常見的面試題,小伙伴們一定要好好記住哈。 1.Servlet是單例的嗎,如何證明? Servlet一般都是單例的,并且是多執行緒的。如何證明Servlet是單例模式呢?很簡單,重寫Servlet的init方法,或者添加一個構造方法。然后,在web ......

    uj5u.com 2023-05-26 08:01:14 more
  • Rocksdb原理簡介

    Rocksdb作為當下nosql中性能的代表被各個存盤組件(mysql、tikv、pmdk、bluestore)作為存盤引擎底座,其基于LSM tree的核心存盤結構(將隨機寫通過資料結構轉化為順序寫)來提供高性能的寫吞吐時保證了讀性能。同時大量的并發性配置來降低compaction的影響。 ......

    uj5u.com 2023-05-26 07:55:59 more
  • 用go封裝一下封禁功能

    本篇為[用go設計開發一個自己的輕量級登錄庫/框架吧]的封禁業務篇,會講講封禁業務的實作,給庫/框架增加新的功能。原始碼:https://github.com/weloe/token-go ......

    uj5u.com 2023-05-26 07:50:53 more
  • 它來了!真正的 python 多執行緒

    哈嘍大家好,我是咸魚 幾天前,IBM 工程師 Martin Heinz 發文表示 python 3.12 版本回引入"Per-Interpreter GIL”,有了這個 Per-Interpreter 全域解釋器鎖,python 就能實作真正意義上的并行/并發 我們知道,python 的多執行緒/行程 ......

    uj5u.com 2023-05-26 07:50:49 more
  • ThreadLocal的應用及原理

    ## 1. ThreadLocal 是什么 JDK 對`ThreadLocal`的描述為: > 此類提供執行緒區域變數。這些變數與普通變數的不同之處在于,每個訪問一個變數的執行緒(通過其get或set方法)都有自己的、獨立初始化的變數副本。ThreadLocal 實體通常是類中的私有靜態欄位,這些欄位希 ......

    uj5u.com 2023-05-26 07:45:35 more