Spring Statemachine 概念及应用

2024-06-24 07:38

本文主要是介绍Spring Statemachine 概念及应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 Finite-state machine

1.1 状态机定义

有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

有限状态机体现了两点:首先是离散的,然后是有限的。

  • State:状态这个词有些难以定义,状态存储关于过去的信息,就是说它反映从系统开始到现在时刻的输入变化。
  • Actions & Transitions:转换指示状态变更,并且用必须满足来确使转移发生的条件来描述它。动作是在给定时刻要进行的活动的描述。
  • Guards:检测器出现的原因是为了检测是否满足从一个状态切换到另外一个状态的条件。
  • Event:事件,又见事件,笼统说来,对系统重要的某件事情被称为事件。

1.2 状态机示例

现实中的例子:验票闸门,来自wiki

旋转门

用于控制地铁和游乐园游乐设施的旋转门是一个门,在腰高处有三个旋转臂,一个横跨入口通道。最初,手臂被锁定,阻挡了入口,阻止了顾客通过。将硬币或代币存放在旋转门上的槽中可解锁手臂,允许单个客户穿过。在顾客通过之后,再次锁定臂直到插入另一枚硬币。

旋转门被视为状态机,有两种可能的状态:锁定和解锁。有两种可能影响其状态的输入:将硬币放入槽(硬币)并推动手臂(推动)。在锁定状态下,推动手臂无效; 无论输入推送次数多少,它都处于锁定状态。投入硬币 - 即给机器输入硬币 - 将状态从锁定转换为解锁。在解锁状态下,放入额外的硬币无效; 也就是说,给予额外的硬币输入不会改变状态。然而,顾客推动手臂,进行推动输入,将状态转回Locked。

旋转门状态机可由状态转换表表示,显示每个可能状态,它们之间的转换(基于给予机器的输入)和每个输入产生的输出:

Current StateInputNext StateOutput
LockedcoinUnlocked解锁旋转门,以便游客能够通过
LockedpushLockedNone
UnlockedcoinUnlockedNone
UnlockedpushLocked当游客通过,锁定旋转门

旋转栅状态机也可以由称为状态图的有向图表示 (上面)。每个状态由节点(圆圈)表示。边(箭头)显示从一个状态到另一个状态的转换。每个箭头都标有触发该转换的输入。不引起状态改变的输入(例如处于未锁定状态的硬币输入)由返回到原始状态的圆形箭头表示。从黑点进入Locked节点的箭头表示它是初始状态。

状态图

2 Spring Statemachine

2.1 定位及特色

Spring Statemachine is a framework for application developers to use state machine concepts with Spring applications. Spring Statemachine 是应用程序开发人员在Spring应用程序中使用状态机概念的框架。

Spring Statemachine 提供如下特色:

  • Easy to use flat one level state machine for simple use cases.(易于使用的扁平单级状态机,用于简单的使用案例。)
  • Hierarchical state machine structure to ease complex state configuration.(分层状态机结构,以简化复杂的状态配置。)
  • State machine regions to provide even more complex state configurations.(状态机区域提供更复杂的状态配置。)
  • Usage of triggers, transitions, guards and actions.(使用触发器、transitions、guards和actions。)
  • Type safe configuration adapter.(应用安全的配置适配器。)
  • Builder pattern for easy instantiation for use outside of Spring Application context(用于在Spring Application上下文之外使用的简单实例化的生成器模式)
  • Recipes for usual use cases(通常用例的手册)
  • Distributed state machine based on a Zookeeper State machine event listeners.(基于Zookeeper的分布式状态机状态机事件监听器。)
  • UML Eclipse Papyrus modeling.(UML Eclipse Papyrus 建模)
  • Store machine config in a persistent storage.(存储状态机配置到持久层)
  • Spring IOC integration to associate beans with a state machine.(Spring IOC集成将bean与状态机关联起来)

2.2 发展及社区

相关资源:

  • Spring Statemachine 主页:Home
  • Spring Statemachine Github:Github,Star:500+ & Fork:200+ (201809)
  • Spring Statemachine Gitter:Gitter, less than 100 people

发布版本:

最新版本:v2.0.2.RELEASE 及 v1.2.12.RELEASE 已经44次版本发布,更详尽和最新情况请查看Github主页。

3 功能示例

3.1 基本功能

继续旋转门的现实例子,添加Maven依赖,本例子中使用版本如下:

<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>2.0.2.RELEASE</version>
</dependency>

定义旋转门所处状态:锁定、解锁,使用枚举

public enum TurnstileStates {Unlocked, Locked
}

定义旋转门操作事件:推门和投币,使用枚举

public enum TurnstileEvents {COIN, PUSH
}

状态机配置,其中turnstileUnlock()和customerPassAndLock()即为当前状态变更后的扩展业务操作,可以根据实际业务场景进行修改

@Configuration
@EnableStateMachine
public class StatemachineConfigurer extends EnumStateMachineConfigurerAdapter<TurnstileStates, TurnstileEvents> {@Overridepublic void configure(StateMachineStateConfigurer<TurnstileStates, TurnstileEvents> states)throws Exception {states.withStates()// 初识状态:Locked.initial(TurnstileStates.Locked).states(EnumSet.allOf(TurnstileStates.class));}@Overridepublic void configure(StateMachineTransitionConfigurer<TurnstileStates, TurnstileEvents> transitions)throws Exception {transitions.withExternal().source(TurnstileStates.Unlocked).target(TurnstileStates.Locked).event(TurnstileEvents.COIN).action(customerPassAndLock()).and().withExternal().source(TurnstileStates.Locked).target(TurnstileStates.Unlocked).event(TurnstileEvents.PUSH).action(turnstileUnlock());}@Overridepublic void configure(StateMachineConfigurationConfigurer<TurnstileStates, TurnstileEvents> config)throws Exception {config.withConfiguration().machineId("turnstileStateMachine");}public Action<TurnstileStates, TurnstileEvents> turnstileUnlock() {return context -> System.out.println("解锁旋转门,以便游客能够通过" );}public Action<TurnstileStates, TurnstileEvents> customerPassAndLock() {return context -> System.out.println("当游客通过,锁定旋转门" );}}

启动类及测试用例

@SpringBootApplication
public class StatemachineApplication implements CommandLineRunner {@Autowiredprivate StateMachine<TurnstileStates, TurnstileEvents> stateMachine;public static void main(String[] args) {SpringApplication.run(StatemachineApplication.class, args);}@Overridepublic void run(String... strings) throws Exception {stateMachine.start();System.out.println("--- coin ---");stateMachine.sendEvent(TurnstileEvents.COIN);System.out.println("--- coin ---");stateMachine.sendEvent(TurnstileEvents.COIN);System.out.println("--- push ---");stateMachine.sendEvent(TurnstileEvents.PUSH);System.out.println("--- push ---");stateMachine.sendEvent(TurnstileEvents.PUSH);stateMachine.stop();}
}

结果输出,与上午所描述的状态机所描述的内容一致。

--- push ---
解锁旋转门,以便游客能够通过
--- push ---
--- coin ---
当游客通过,锁定旋转门
--- coin ---

3.2 实用功能

3.2.1 状态存储

状态机持久化,实际环境中,当前状态往往都是从持久化介质中实时获取的,Spring Statemachine通过实现StateMachinePersist接口,write和read当前状态机的状态

本例中,使用的是HashMap作为模拟存储介质,正式项目中需要使用真实的状态获取途径

@Component
public class BizStateMachinePersist implements StateMachinePersist<TurnstileStates, TurnstileEvents, Integer> {static Map<Integer, TurnstileStates> cache = new HashMap<>(16);@Overridepublic void write(StateMachineContext<TurnstileStates, TurnstileEvents> stateMachineContext, Integer integer) throws Exception {cache.put(integer, stateMachineContext.getState());}@Overridepublic StateMachineContext<TurnstileStates, TurnstileEvents> read(Integer integer) throws Exception {// 注意状态机的初识状态与配置中定义的一致return cache.containsKey(integer) ?new DefaultStateMachineContext<>(cache.get(integer), null, null, null, null, "turnstileStateMachine") :new DefaultStateMachineContext<>(TurnstileStates.Locked, null, null, null, null, "turnstileStateMachine");}
}

在StatemachineConfigurer中发布

@Autowired
private BizStateMachinePersist bizStateMachinePersist;@Bean
public StateMachinePersister<TurnstileStates, TurnstileEvents, Integer> stateMachinePersist() {return new DefaultStateMachinePersister<>(bizStateMachinePersist);
}

在StatemachineApplication中使用,自动注入StateMachinePersister对象,测试用例如下

stateMachine.start();stateMachinePersist.restore(stateMachine, 1);
System.out.println("--- push ---");
stateMachine.sendEvent(TurnstileEvents.PUSH);
stateMachinePersist.persist(stateMachine, 1);stateMachinePersist.restore(stateMachine, 1);
System.out.println("--- push ---");
stateMachine.sendEvent(TurnstileEvents.PUSH);
stateMachinePersist.persist(stateMachine, 1);stateMachinePersist.restore(stateMachine, 1);
System.out.println("--- coin ---");
stateMachine.sendEvent(TurnstileEvents.COIN);
stateMachinePersist.persist(stateMachine, 1);stateMachinePersist.restore(stateMachine, 1);
System.out.println("--- coin ---");
stateMachine.sendEvent(TurnstileEvents.COIN);
stateMachinePersist.persist(stateMachine, 1);stateMachine.stop();

3.2.2 动作监听

定义动作监听类,StatemachineMonitor(名称随意),添加注解@WithStateMachine。本例中使用id进行状态机绑定,根据文档定义,可以使用name和id两种属性绑定需要监听的状态机实例。如果不定义任何name或者id,默认监听名称为stateMachine的状态机。

@WithStateMachine(id = "turnstileStateMachine")
public class StatemachineMonitor {@OnTransitionpublic void anyTransition() {System.out.println("--- OnTransition --- init");}@OnTransition(target = "Unlocked")public void toState1() {System.out.println("--- OnTransition --- toState1");}@OnStateChanged(source = "Unlocked")public void fromState1() {System.out.println("--- OnTransition --- fromState1");}
}

其他Context的事件监听,后续文章进行描述,官网链接

3.2.2 状态机工程

实际业务环境中,往往是多线程处理不同的业务ID对应的状态,状态机中利用事件的context传递数据,会出现多线程问题,需要利用状态机工程,利用UUID创建不同状态机。

在StatemachineConfigurer类中,修改@EnableStateMachine@EnableStateMachineFactory,同时添加状态机处理动作封装方法,读者可以根据业务场景定制,本例为一种可行方案

@Service
public class StatemachineService {@Autowiredprivate StateMachinePersister<TurnstileStates, TurnstileEvents, Integer> stateMachinePersist;@Autowiredprivate StateMachineFactory<TurnstileStates, TurnstileEvents> stateMachineFactory;public void execute(Integer businessId, TurnstileEvents event, Map<String, Object> context) {// 利用随记ID创建状态机,创建时没有与具体定义状态机绑定StateMachine<TurnstileStates, TurnstileEvents> stateMachine = stateMachineFactory.getStateMachine(UUID.randomUUID());stateMachine.start();try {// 在BizStateMachinePersist的restore过程中,绑定turnstileStateMachine状态机相关事件监听stateMachinePersist.restore(stateMachine, businessId);// 本处写法较为繁琐,实际为注入Map<String, Object> context内容到message中MessageBuilder<TurnstileEvents> messageBuilder = MessageBuilder.withPayload(event).setHeader("BusinessId", businessId);if (context != null) {context.entrySet().forEach(p -> messageBuilder.setHeader(p.getKey(), p.getValue()));}Message<TurnstileEvents> message = messageBuilder.build();// 发送事件,返回是否执行成功boolean success = stateMachine.sendEvent(message);if (success) {stateMachinePersist.persist(stateMachine, businessId);} else {System.out.println("状态机处理未执行成功,请处理,ID:" + businessId + ",当前context:" + context);}} catch (Exception e) {e.printStackTrace();} finally {stateMachine.stop();}}
}

完整示例参见:链接

4 参考示例

  • Spring Statemachine Github
  • Finite-state machine维基百科
  • 有限状态机百度百科
  • Spring Statemachine Reference



作者:shoukai
链接:https://www.jianshu.com/p/9ee887e045dd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于Spring Statemachine 概念及应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089542

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2