Spring StateMachine采用重量级锁保证线程安全,有状态
这里的有无状态是指:
有状态:实例内部保存当前状态(<font style="color:rgb(0, 0, 0);">current state</font>)和上下文信息。状态转换时,实例内部状态会自动更新。如果不使用线程池,那么每次都会创建new一个状态机对象,然后默认为起始状态
无状态:状态都是外部明确传入的,我们只需要他的状态流转,而状态机不保存当前事物的状态,表现了它可以被 单例化 ,表明了状态机是可以重复利用的
无状态优缺点:
- 优点:
资源利用率高(单例即可),适合分布式系统。 - 缺点:
状态需外部管理,代码侵入性较强。
核心概念
:::info
- State:状态
- Event:事件,状态由事件触发,引起变化
- Transition:流转,表示从一个状态到另一个状态
- External Transition:外部流转,两个不同状态之间的流转
- Internal Transition:内部流转,同一个状态之间的流转
- Condition:条件,表示是否允许到达某个状态
- Action:动作,到达某个状态之后,可以做什么
- StateMachine:状态机
:::
顶层接口
1 | public interface StateMachine<S, E, C> extends Visitable{ |
接口声明了3个泛型类,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">S</font>**表示状态类型,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">E</font>**表示事件类型,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">C</font>**表示上下文类型
:::info
S、E:核心流转条件
C: 状态机的上下文信息,使用上下文信息,我们可以在状态转换过程中传递一些额外的数据和参数,方便进行状态机的状态转换和业务逻辑的处理。
:::
状态变更方法
- fireEvent(S sourceState, E event, C ctx):将指定事件发送给状态机,触发状态转换,并返回目标状态。该方法是状态机进行状态转换的核心逻辑,可以根据当前状态和事件来计算出目标状态,并执行相应的动作或逻辑。
1 | 触发事件 (fireEvent) |
- 条件优先原则:状态机会先进行
<font style="color:rgb(0, 0, 0);">when()</font>条件判断,只有条件满足时才会执行后续操作
- 原子性保证:整个过程是原子性的,条件判断 → 执行业务动作 → 状态变更 是一个完整的事务单元
- 状态变更时机:状态变为
<font style="color:rgb(0, 0, 0);">to</font>的状态仅发生在所有条件通过且业务动作执行完成之后
PS:内部状态流转
虽然不管when是否成立都是保持当前的状态,但是为了fireEvent的时候确定要执行的Transition,所以内部流转也是要写当前状态的,不写不知道是哪个状态的内部流转
参考文档:
https://www.cnblogs.com/johnnyzen/p/18406389
工作流比较
- 工作流(WorkFlow),大体是指业务过程(整体或者部分)在计算机应用环境下的自动化,是对工作流程及其各操作步骤之间业务规则的描述。在计算机系统中,工作流属于计算机支持的协同工作(CSCW)的一部分。
- 状态机是工作流(WorkFlow)的一种类型,包括顺序工作流(Sequential)和状态机工作流(State Machine)。
| 特性 | 状态机(State Machine) | 工作流(WorkFlow) |
|---|---|---|
| 关注点 | 单个任务 | 状态流转 |
| 循环 | 可以简单的实现循环 | 无循环 |
| 实现难度 | 比较麻烦,需要记录任务当前状态 | 实现简单 |
| 表达方式 | 表达更灵活 | 串行表达,不是很灵活 |
| 运行效率 | 高 | 低,可并行 |
状态机实战
最基础的构建一个流转:
1 | StateMachineBuilder<OrderStatusEnum, OrderEventEnum, OrderContext> builder |
我们将其修改为注解类型实现,更优雅一些,创建组件注解

然后我们在创建这个状态机实例的时候,就装载好所有的流转

组装方法例子

最后的action部分用来组装perform的执行逻辑

写一个factory,将所有的状态机实例放到map中

前面的装载是放到了构造中,我们测试时new来使用。
实际中,我们可以直接写在BeanPostProcesor中,创建bean的时候如果扫描到相应注解,做对应封装,封装方法是完全一致的

实际使用时,创建流转直接返回Action即可

最后从业务组件,调用到某个处理引擎,然后处理引擎实际调用fireevent来触发流转和业务底层处理

fireEvent双要素
:::info
此处和泛化调用、反射笔记中都有这种要素,就是根据什么,能确定我的invoke方法、泛化方法、还有流转
:::
双要素:S+E 当前状态+触发事件 = 确定唯一的流转
为什么我们可以直接把创建的实例直接用class作为map的key进行存储呢?
原因是我们只需要拿到一个状态机实例,然后
本质从StateMachineFactory.get(machineId) 获取状态机,不允许重复构建,重复构建也会报错。
参考:https://www.cnblogs.com/Zero-Jo/p/14622937.html
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !