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)

posted @ 2021-05-17 10:26  蒋月生  阅读(4131)  评论(2编辑  收藏  举报