Spring StateMachine 让状态机结构更加层次化,可以帮助开发者简化状态机的开发过程。

之前,我们使用二维数组实现状态机机制,现在,我们来用 Spring StateMachine 进行改造。

环境依赖

修改 POM 文件,添加 spring-statemachine-core 依赖。

  1. <dependency>
  2. <groupId>org.springframework.statemachine</groupId>
  3. <artifactId>spring-statemachine-core</artifactId>
  4. <version>1.2.0.RELEASE</version>
  5. </dependency>

状态和事件

现在,我以用户注册为案例,来讲解状态和事件之间的状态机机制。

状态枚举

注册有哪些状态呢,我们来想想,应该有4个状态:未连接、已连接、注册中、已注册。

  1. public enum RegStatusEnum {
  2. // 未连接
  3. UNCONNECTED,
  4. // 已连接
  5. CONNECTED,
  6. // 注册中
  7. REGISTERING,
  8. // 已注册
  9. REGISTERED;
  10. }

事件枚举

相对应的,存在几个核心事件:连接、注册、注册成功、注册失败、注销。

  1. public enum RegEventEnum {
  2. // 连接
  3. CONNECT,
  4. // 注册
  5. REGISTER,
  6. // 注册成功
  7. REGISTER_SUCCESS,
  8. // 注册失败
  9. REGISTER_FAILED,
  10. // 注销
  11. UN_REGISTER;
  12. }

状态机配置

  1. @Configuration
  2. @EnableStateMachine
  3. public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
  4. }

@EnableStateMachine注解,标识启用 Spring StateMachine 状态机功能。

初始化状态机状态

我们需要初始化状态机的状态。

  1. @Override
  2. public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
  3. states.withStates()
  4. // 定义初始状态
  5. .initial(RegStatusEnum.UNCONNECTED)
  6. // 定义状态机状态
  7. .states(EnumSet.allOf(RegStatusEnum.class));
  8. }

其中,initial(RegStatusEnum.UNCONNECTED) 定义了初始状态是未连接状态。states(EnumSet.allOf(RegStatusEnum.class)) 定义了定义状态机中存在的所有状态。

初始化状态迁移事件

我们需要初始化当前状态机有哪些状态事件。

  1. @Override
  2. public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions)
  3. throws Exception {
  4. transitions
  5. // 1.连接事件
  6. // 未连接 -> 已连接
  7. .withExternal()
  8. .source(RegStatusEnum.UNCONNECTED)
  9. .target(RegStatusEnum.CONNECTED)
  10. .event(RegEventEnum.CONNECT)
  11. .and()
  12. // 2.注册事件
  13. // 已连接 -> 注册中
  14. .withExternal()
  15. .source(RegStatusEnum.CONNECTED)
  16. .target(RegStatusEnum.REGISTERING)
  17. .event(RegEventEnum.REGISTER)
  18. .and()
  19. // 3.注册成功事件
  20. // 注册中 -> 已注册
  21. .withExternal()
  22. .source(RegStatusEnum.REGISTERING)
  23. .target(RegStatusEnum.REGISTERED)
  24. .event(RegEventEnum.REGISTER_SUCCESS)
  25. .and()
  26. // 5.注销事件
  27. // 已连接 -> 未连接
  28. .withExternal()
  29. .source(RegStatusEnum.CONNECTED)
  30. .target(RegStatusEnum.UNCONNECTED)
  31. .event(RegEventEnum.UN_REGISTER)
  32. .and()
  33. // 注册中 -> 未连接
  34. .withExternal()
  35. .source(RegStatusEnum.REGISTERING)
  36. .target(RegStatusEnum.UNCONNECTED)
  37. .event(RegEventEnum.UN_REGISTER)
  38. .and()
  39. // 已注册 -> 未连接
  40. .withExternal()
  41. .source(RegStatusEnum.REGISTERED)
  42. .target(RegStatusEnum.UNCONNECTED)
  43. .event(RegEventEnum.UN_REGISTER)
  44. ;
  45. }

这里,我以连接事件为案例,其中 source 指定原始状态,target 指定目标状态,event 指定触发事件。

因此,下面的状态就很好理解了,即当发生连接事件时,从未连接状态变更为已连接状态。

  1. // 未连接 -> 已连接
  2. .withExternal()
  3. .source(RegStatusEnum.UNCONNECTED)
  4. .target(RegStatusEnum.CONNECTED)
  5. .event(RegEventEnum.CONNECT)

状态监听器

Spring StateMachine 提供了注解配置实现方式,所有 StateMachineListener 接口中定义的事件都能通过注解的方式来进行配置实现。

  1. @WithStateMachine
  2. public class StateMachineEventConfig {
  3. @OnTransition(source = "UNCONNECTED", target = "CONNECTED")
  4. public void connect() {
  5. System.out.println("///////////////////");
  6. System.out.println("连接事件, 未连接 -> 已连接");
  7. System.out.println("///////////////////");
  8. }
  9. @OnTransition(source = "CONNECTED", target = "REGISTERING")
  10. public void register() {
  11. System.out.println("///////////////////");
  12. System.out.println("注册事件, 已连接 -> 注册中");
  13. System.out.println("///////////////////");
  14. }
  15. @OnTransition(source = "REGISTERING", target = "REGISTERED")
  16. public void registerSuccess() {
  17. System.out.println("///////////////////");
  18. System.out.println("注册成功事件, 注册中 -> 已注册");
  19. System.out.println("///////////////////");
  20. }
  21. @OnTransition(source = "REGISTERED", target = "UNCONNECTED")
  22. public void unRegister() {
  23. System.out.println("///////////////////");
  24. System.out.println("注销事件, 已注册 -> 未连接");
  25. System.out.println("///////////////////");
  26. }
  27. }

这里,我仍然以连接事件为案例,@OnTransition 中 source 指定原始状态,target 指定目标状态,当事件触发时将会被监听到从而调用 connect() 方法。

总结

Spring StateMachine 让状态机结构更加层次化,可以帮助开发者简化状态机的开发过程。

我们来回顾下几个核心步骤

  • 定义状态枚举。
  • 定义事件枚举。
  • 定义状态机配置,设置初始状态,以及状态与事件之间的关系。
  • 定义状态监听器,当状态变更时,触发方法。

源代码

相关示例完整代码: springboot-action

(完)

如果觉得我的文章对你有帮助,请随意打赏。

Spring Boot 揭秘与实战(七) 实用技术篇 - StateMachine 状态机机制的更多相关文章

  1. Spring Boot 揭秘与实战(七) 实用技术篇 - 异步任务

    文章目录 1. Spring Boot 集成异步任务 2. 单发服务模式 3. 请求应答模式 4. 源代码 Spring 对异步任务具有很好的支持.这篇文章,我们透过 Spring Boot 来讲解下 ...

  2. Spring Boot 揭秘与实战(七) 实用技术篇 - Java Mail 发送邮件

    文章目录 1. Spring Boot 集成 Java Mail 2. 单元测试 3. 源代码 Spring 对 Java Mail 有很好的支持.因此,Spring Boot 也提供了自动配置的支持 ...

  3. Spring Boot 揭秘与实战(七) 实用技术篇 - FreeMarker 模板引擎

    文章目录 1. FreeMaker 代替 JSP 作为页面渲染 2. 生成静态文件 3. 扩展阅读 4. 源代码 Spring Boot 提供了很多模板引擎的支持,例如 FreeMarker.Thym ...

  4. Spring Boot 揭秘与实战 附录 - Spring Boot 公共配置

    Spring Boot 公共配置,配置 application.properties/application.yml 文件中. 摘自:http://docs.spring.io/spring-boot ...

  5. Spring Boot 揭秘与实战 自己实现一个简单的自动配置模块

    文章目录 1. 实战的开端 – Maven搭建 2. 参数的配置 - 属性参数类 3. 真的很简单 - 简单的服务类 4. 自动配置的核心 - 自动配置类 5. spring.factories 不要 ...

  6. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

  7. Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏玄机

    文章目录 1. 开箱即用,内藏玄机 2. 总结 3. 源代码 Spring Boot提供了很多”开箱即用“的依赖模块,那么,Spring Boot 如何巧妙的做到开箱即用,自动配置的呢? 开箱即用,内 ...

  8. Spring Boot 揭秘与实战(九) 应用监控篇 - 自定义监控端点

    文章目录 1. 继承 AbstractEndpoint 抽象类 2. 创建端点配置类 3. 运行 4. 源代码 Spring Boot 提供的端点不能满足我们的业务需求时,我们可以自定义一个端点. 本 ...

  9. Spring Boot 揭秘与实战(九) 应用监控篇 - HTTP 健康监控

    文章目录 1. 内置 HealthIndicator 监控检测 2. 自定义 HealthIndicator 监控检测 3. 源代码 Health 信息是从 ApplicationContext 中所 ...

随机推荐

  1. Lunx下 怎样启动和关闭oracle数据库

    1.因为oracle运行在Linux系统下,首先,要连接Linux系统 2.切换到oracle安装用户下. 我的是 ora12. 3.运行oracle的环境变量, 以便输入相关命令. 4.进入orac ...

  2. 牛客第二场A-run

    链接:https://www.nowcoder.com/acm/contest/140/A 来源:牛客网 White Cloud is exercising in the playground. Wh ...

  3. element-ui 表格翻页多选,数据回显

    reserve-selection与row-key结合 <el-table :data="pageData" ref="goodsTable" size= ...

  4. js 日期格式: UTC GMT 互相转换

    //UTC 转指定格式日期 let date = '2018-03-07T16:00:00.000Z' console.log(moment(date).format('YYYY-MM-DD HH:m ...

  5. Qt之获取子部件

    Qt5.10 QList<QObject*> list_children = this->children(); for(int i=0;i<list_children.siz ...

  6. Use of undefined constant FTP_BINARY - assumed 'FTP_BINARY

    用Laravel中的filesystems里面的ftp上传文件时报错.在windows上开发,文件上传的时候碰到上面的问题,搜了些资料,发现是php7的ftp拓展默认未开启. 第一步:检查extens ...

  7. Uva 11520 - Fill the Square 贪心 难度: 0

    题目 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&a ...

  8. jsp下载excel文件

    jsp下载excel文件的的实现方法很多,今天也遇到这个问题,乱敲了一阵,终于搞定了,记下来和朋友们分享吧. 假设需要下载excel文件的jsp页面名为:down.jsp 对应的后台action名为: ...

  9. Date和Timestamp区别

    主要是精度问题,date没有ms,而timestamp是有ms的,所以date的精度要低于timestamp. 而且二者可以互相转换. 除此之外,没有什么不同,

  10. fedora网络设置

    一:网络设置 1.找到要设置的网卡 命令:ip addr 列出所有的网络配置,找到你需要配置的网卡 入图,我这个是ens33 2.找到配置文件 配置文件路径: /etc/sysconfig/netwo ...