Spring statemachine的使用總結


Spring StateMachine使用總結

在工作過程中需要使用狀態機,因此去看了一段時間官網,結合別人的例子,總算是折騰出來了,記錄一下

簡單介紹

  1. 狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型.

  2. 按照個人的理解狀態機就是某個事物由於各種不同的事件觸發導致該事物的狀態發生了改變.例如一個熱水壺,他的初始狀態是關閉的,我們發送一個打開開關的事件,並且只有當滿足了壺中有水的條件然后才會執行燒水的動作.經過這樣一個轉換的過程,熱水壺的狀態變成了打開.我們再做一個事件.由此,我們就能知道狀態機總共有狀態,事件,活動(動作)和轉移四大部分組成.

  3. 更加詳細的解釋請百度.

    image-20210517100859040

具體使用

  1. spring statemachine 2.4.0的官方文檔是Spring Statemachine - Reference Documentation

  2. spring statemachine提供了兩種構建方式:

    1. 第一種:使用papyrus這個軟件畫出你所需要的狀態模型,如下圖:

      1. image-20210513142508957
    2. ,該模型所在的uml文件可以直接導入springboot工程,開發人員可以很方便的使用它,例如:

      1. image-20210513142242339
    3. papyrus的具體使用在Spring Statemachine - Reference Documentation 有較為詳細的說明,在這里介紹一下繪制uml的流程和幾個重要的點:

      1. 第一步先畫出你需要的幾個state.

      2. 使用Transition將各個state按照業務需求連接.在Transition上綁定Trigger(觸發器)

        1. image-20210513144228262
      3. 可以在state中綁定進入狀態和退出狀態的事件,,如下:

        1. image-20210513144900667
        2. image-20210513145019706
        3. image-20210513145158195
      4. 第二種構建狀態機的方式是通過直接寫代碼構建每個狀態以及事件,本篇不做敘述:如下

        1. @Override
          public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
          		throws Exception {
          	transitions
          		.withExternal()
          			.source(States.S1).target(States.S1).event(Events.A)
          			.guard(foo1Guard())
          			.and()
          		.withExternal()
          			.source(States.S1).target(States.S11).event(Events.B)
          			.and()
          		.withExternal()
          			.source(States.S21).target(States.S211).event(Events.B)
          			.and()
          		.withExternal()
          			.source(States.S1).target(States.S2).event(Events.C)
          			.and()
          		.withExternal()
          			.source(States.S2).target(States.S1).event(Events.C)
          			.and()
          		.withExternal()
          			.source(States.S1).target(States.S0).event(Events.D)
          			.and()
          		.withExternal()
          			.source(States.S211).target(States.S21).event(Events.D)
          			.and()
          		.withExternal()
          			.source(States.S0).target(States.S211).event(Events.E)
          			.and()
          		.withExternal()
          			.source(States.S1).target(States.S211).event(Events.F)
          			.and()
          		.withExternal()
          			.source(States.S2).target(States.S11).event(Events.F)
          			.and()
          		.withExternal()
          			.source(States.S11).target(States.S211).event(Events.G)
          			.and()
          		.withExternal()
          			.source(States.S211).target(States.S0).event(Events.G)
          			.and()
          		.withInternal()
          			.source(States.S0).event(Events.H)
          			.guard(foo0Guard())
          			.action(fooAction())
          			.and()
          		.withInternal()
          			.source(States.S2).event(Events.H)
          			.guard(foo1Guard())
          			.action(fooAction())
          			.and()
          		.withInternal()
          			.source(States.S1).event(Events.H)
          			.and()
          		.withExternal()
          			.source(States.S11).target(States.S12).event(Events.I)
          			.and()
          		.withExternal()
          			.source(States.S211).target(States.S212).event(Events.I)
          			.and()
          		.withExternal()
          			.source(States.S12).target(States.S212).event(Events.I);
          
          }
          

    在springboot中的怎么使用狀態機

    1. 當設計人員設計好新的狀態機uml后,先導入uml文件.

    2. 編寫配置類,一般有兩個配置類要編寫

      1. 持久化配置類

        package com.hdstcloud.meter.statemachineModule.config.uml;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.data.redis.connection.RedisConnectionFactory;
        import org.springframework.statemachine.StateMachinePersist;
        import org.springframework.statemachine.persist.RepositoryStateMachinePersist;
        import org.springframework.statemachine.redis.RedisStateMachineContextRepository;
        import org.springframework.statemachine.redis.RedisStateMachinePersister;
        
        /**
         * @author :js
         * @date :Created in 2021-03-02 9:38
         * @description: 持久化配置
         * @version: 1.0
         */
        @Configuration
        public class MeterPersistUmlConfig {
        
            @Autowired
            private RedisConnectionFactory redisConnectionFactory;
        
        
            /**
             * 注入RedisStateMachinePersister對象
             *
             * @return
             */
            @Bean(name = "meterRedisUmlPersister")
            public RedisStateMachinePersister<String, String> meterRedisPersister() {
                return new RedisStateMachinePersister<>(meterRedisPersist());
            }
        
            /**
             * 通過redisConnectionFactory創建StateMachinePersist
             *
             * @return
             */
            public StateMachinePersist<String, String, String> meterRedisPersist() {
                RedisStateMachineContextRepository<String, String> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
                return new RepositoryStateMachinePersist<>(repository);
            }
        }
        
        
      2. 狀態機的總配置類

        package com.hdstcloud.meter.statemachineModule.config.uml;
        
        import com.hdstcloud.meter.statemachineModule.common.BizUniformResult;
        import com.hdstcloud.meter.statemachineModule.common.ConditionConst;
        import com.hdstcloud.meter.statemachineModule.common.EventConst;
        import com.hdstcloud.meter.statemachineModule.config.StateMachineEventListener;
        import com.hdstcloud.meter.statemachineModule.service.uml.MeterStateMachineBizInter;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Scope;
        import org.springframework.statemachine.action.Action;
        import org.springframework.statemachine.config.EnableStateMachineFactory;
        import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
        import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
        import org.springframework.statemachine.config.builders.StateMachineModelConfigurer;
        import org.springframework.statemachine.config.model.StateMachineModelFactory;
        import org.springframework.statemachine.guard.Guard;
        import org.springframework.statemachine.uml.UmlStateMachineModelFactory;
        import org.springframework.util.ObjectUtils;
        
        import javax.annotation.Resource;
        
        /**
         * @author :js
         * @date :Created in 2021-02-20 13:56
         * @description: 表計狀態機配置
         * @version:1.0
         */
        @Configuration
        @Slf4j
        @EnableStateMachineFactory(name = "meterStateMachineFactory")
        @Scope("prototype")
        public class MeterStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
        
            @Resource
            private StateMachineEventListener stateMachineEventListener;
        
            //表計狀態機id
            public static final String meterStateMachineId = "meter";
        
            @Autowired
            private MeterStateMachineBizInter meterStateMachineBizInter;
        
        
            @Override
            public void configure(StateMachineConfigurationConfigurer<String, String> config)
                    throws Exception {
                config
                        .withConfiguration().listener(stateMachineEventListener)   //監聽狀態機運行的類
                        .autoStartup(true).machineId(meterStateMachineId);  
            }
        
            @Override
            public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
                model
                        .withModel()
                        .factory(meterFactory());
            }
        
            //導入uml文件
            @Bean
            public StateMachineModelFactory<String, String> meterFactory() {
                return new UmlStateMachineModelFactory("classpath:Meter_State_Machine.uml");
            }
        
            /**
             * 安裝業務,當我們在controller發送了安裝事件代碼就會運行到這里來,下面的事件也是如此
             *
             * @return
             */
            @Bean
            public Action<String, String> installBiz() {
                return context -> {
                    log.info("安裝測試!");
                    try {
                        //安裝業務執行
                        meterStateMachineBizInter.install(context.getMessage());
                        BizUniformResult result = (BizUniformResult) context.getMessageHeader(ConditionConst.DTO);
                        if (result.getFlag()) {
                            //發送安裝完成
                            context.getStateMachine().sendEvent(EventConst.INSTALL_COMPLETED);
                        } else {
                            //安裝失敗
                            context.getStateMachine().sendEvent(EventConst.INSTALL_FAIL);
                        }
                    } catch (Exception e) {
                        log.error("安裝業務出現異常:{}", e.getMessage());
                        context.getStateMachine().hasStateMachineError();
                        context.getStateMachine().sendEvent(EventConst.INSTALL_FAIL);
                    }
                };
            }
        
            /**
             * 拆表測試
             *
             * @return
             */
            @Bean
            public Action<String, String> splitMeterBiz() {
                return context -> {
                    log.info("拆表測試!");
                    try {
                        //解綁業務執行
                        meterStateMachineBizInter.splitMeter(context.getMessage());
                        BizUniformResult result = (BizUniformResult) context.getMessageHeader(ConditionConst.DTO);
                        if (result.getFlag()) {
                            //發送拆表完成
                            context.getStateMachine().sendEvent(EventConst.SPLIT_METER_COMPLETED);
                        } else {
                            //拆表失敗
                            log.info("拆表失敗!");
                        }
                    } catch (Exception e) {
                        log.error("拆表業務出現異常:{}", e.getMessage());
                        log.info("拆表失敗!");
                    }
                };
            }
        
            /**
             * 檢定測試
             *
             * @return
             */
            @Bean
            public Action<String, String> docimasyBiz() {
                return context -> {
                    log.info("檢定測試!");
                    boolean flag;
                    try {
                        //檢定業務執行
                        flag = meterStateMachineBizInter.docimasy(context.getMessage());
                        if (flag) {
                            //發送檢定完成
                            context.getStateMachine().sendEvent(EventConst.DOCIMASY_COMPLETE);
                        } else {
                            //檢定失敗
                            log.info("檢定失敗!");
                            //如果是從安裝過來的
                            if (ConditionConst.INSTALLIERT.equals(context.getMessageHeader(ConditionConst.PREMISE))) {
                                context.getStateMachine().sendEvent(EventConst.DOCIMASY_FAIL_TO_INSTALL);
                                //如果從未安裝過來的
                            } else if (ConditionConst.UNINSTALLIERT.equals(context.getMessageHeader(ConditionConst.PREMISE))) {
                                context.getStateMachine().sendEvent(EventConst.DOCIMASY_FAIL_TO_UNINSTALL);
                            }
                        }
                    } catch (Exception e) {
                        log.error("出現異常");
                        log.info("檢定失敗!");
                    }
                };
            }
        
            /**
             * 檢定完成狀態到安裝或者未安裝的判斷條件
             *
             * @ return
             */
            @Bean
            public Guard<String, String> needInstallGuard() {
                return context -> ObjectUtils.nullSafeEquals(ConditionConst.INSTALLIERT, context.getMessageHeaders().get(ConditionConst.CHOICE, String.class));
            }
        
            /**
             * 檢定到已安裝所作的業務
             *
             * @ return
             */
            @Bean
            public Action<String, String> reinstallation() {
                return context -> {
                    //檢定到已安裝所作的業務
                    meterStateMachineBizInter.reinstallation(context.getMessage());
                };
            }
        }
        
      3. 監聽狀態機運行的類

        package com.hdstcloud.meter.statemachineModule.config;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.messaging.Message;
        import org.springframework.statemachine.StateMachine;
        import org.springframework.statemachine.listener.StateMachineListenerAdapter;
        import org.springframework.statemachine.state.State;
        import org.springframework.stereotype.Component;
        
        /**
         * @author :js
         * @date :Created in 2021-03-09 13:35
         * @description: 狀態機監聽器
         * @version: 1.0
         */
        @Component
        @Slf4j
        public class StateMachineEventListener extends StateMachineListenerAdapter<String, String> {
        
            /**
             * 狀態機出錯
             */
            @Override
            public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
                String state = stateMachine.getState().getId();
                log.error("狀態機出錯!,錯誤信息為:" + exception.getMessage());
                log.error("狀態機當前的狀態為;" + state);
            }
        
            /**
             * 狀態改變
             *
             * @param from
             * @param to
             */
            @Override
            public void stateChanged(State<String, String> from, State<String, String> to) {
                if (from == null && to == null) {
                    log.info("狀態機出問題啦,沒有找到該事件的對應狀態轉移情況!");
                }
        //        if (from == null && to != null) {
        //            log.info("現在狀態機在{}狀態", to.getId());
        //        }
                if (from != null && to != null) {
                    log.info("現在狀態機從{}跳轉到{}", from.getId(), to.getId());
                }
                if (from != null && to == null) {
                    log.info("現在狀態機從{}跳轉到未知", from.getId());
                }
            }
        
            /**
             * 事件不接收
             *
             * @param event
             */
            @Override
            public void eventNotAccepted(Message<String> event) {
                log.info("當前狀態不能接收{}事件!", event.getPayload());
            }
        }
        
        
    3. 下面的代碼交代了我們從controller層接收到一個請求,該怎么使用狀態機.

    4. @RestController
      public class TestController {
      
      	@Autowired
      	@Qualifier("meterStateMachineFactory")
      	StateMachineFactory<String, String> meterStateMachineFactory;
      
      	@Autowired
      	@Qualifier("mpStateMachineFactory")
      	StateMachineFactory<String, String> mpStateMachineFactory;
      
      	@Autowired
      	@Qualifier("meterRedisUmlPersister")
      	private StateMachinePersister<String, String, String> meterPersistUmlConfig;
      
      	@Autowired
      	@Qualifier("mpRedisUmlPersister")
      	private StateMachinePersister<String, String, String> mpPersistUmlConfig;
      
      	@RequestMapping("/test")
      	public void test(String id) throws Exception {
      
      		User user = new User();
      		user.setId(id);
      		user.setName("ASA");
      
      		//業務統一結果集,用來傳遞業務數據
      		BizUniformResult<Object> result1 = new BizUniformResult<>();
               //把user對象塞入業務統一結果集
      		result1.setData(user);
              
      		//初始化一個狀態機,他的狀態是初始狀態
      		StateMachine<String, String> stateMachine = meterStateMachineFactory.getStateMachine("meter");
      
      		//從是redis數據庫中還原狀態機
      		meterPersistUmlConfig.restore(stateMachine, user.getId());
      		System.out.println("meter恢復狀態機后的狀態為:" + stateMachine.getState().getId());
      
      		//封裝安裝業務需要的信息
      		Message<String> message = MessageBuilder.withPayload(EventConst.INSTALL).setHeader(ConditionConst.DTO, result1).setHeader("userObj", "userObjValue")
      				.setHeader("mpId", "as").build();
               //發送安裝事件
      		stateMachine.sendEvent(message);
      		//返回結果
      		BizUniformResult result = (BizUniformResult) message.getHeaders().get(ConditionConst.DTO);
      		//發送維護完成事件
      		boolean b = stateMachine.sendEvent(EventConst.MAINTAIN_COMPLETE);
      		System.out.println(b + "維護完成事件成功與否");
      		
      		Message<String> message1 = MessageBuilder.withPayload(EventConst.SPLIT_METER).setHeader(ConditionConst.DTO, result1).build();
      		//拆表
      		stateMachine.sendEvent(message1);
      		//檢定
      		stateMachine.sendEvent(MessageBuilder.withPayload(EventConst.
      				DOCIMASY).setHeader(ConditionConst.PREMISE, ConditionConst.UNINSTALLIERT).build());
      		//維護
      		stateMachine.sendEvent(EventConst.MAINTAIN);
      
      		//把更改后的狀態機使用redis持久化
      		meterPersistUmlConfig.persist(stateMachine, user.getId());
      
      		/**
      		 * 計量點狀態機
      		 */
      		StateMachine<String, String> mpMachine = mpStateMachineFactory.getStateMachine("mp1");
      		mpPersistUmlConfig.restore(mpMachine, "mp1");
      		System.out.println("mp恢復狀態機后的狀態為:" + mpMachine.getState().getId());
      		//安裝
      		Message<String> m1 = MessageBuilder.withPayload(EventConst.OPEN).setHeader("user", user).setHeader("userObj", "userObjValue")
      				.setHeader("mpId", "as").build();
      		mpMachine.sendEvent(m1);
      		mpMachine.setStateMachineError(new Exception());
      		//redis持久化
      		mpPersistUmlConfig.persist(mpMachine, "mp1");
      	}
      
      }
      
      
    5. BizUniformResult類的組成如下:

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class BizUniformResult<T> {
      
          /**
           * 業務成功與否
           */
          private Boolean flag;
          /**
           * 描述信息
           */
          private String description;
          /**
           * 返回結果
           */
          private T data;
      
      
          public BizUniformResult(String description, T data) {
              if (this.checkDataExist(data)) {
                  this.description = description;
                  this.flag = true;
                  this.data = data;
              } else {
                  this.description = description;
                  this.flag = false;
              }
          }
      
          public BizUniformResult(String description, boolean flag) {
              if (flag) {
                  this.description = description + "成功";
              } else {
                  this.description = description + "失敗";
              }
              this.flag = flag;
          }
      
      
          private boolean checkDataExist(T data) {
              if (isNull(data)) {
                  return false;
              } else if (List.class.isAssignableFrom(data.getClass()) && ((List) data).isEmpty()) {
                  return false;
              } else {
                  return !Map.class.isAssignableFrom(data.getClass()) || !((Map) data).isEmpty();
              }
          }
      
          public static boolean isNull(Object... objs) {
              for (Object obj : objs) {
                  if (Objects.isNull(obj)) {
                      return true;
                  }
              }
              return false;
          }
      }
      
    6. 以上是spring statemachine的基本使用,文中代碼的源碼可以訪問 spring statemachine: 狀態機的使用 (gitee.com)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM