还在用ifelse来写业务?了解下Spring状态机
状态机之所以强大,是因为其行为在启动时就以固定的方式定义了操作规则,从而确保了一贯的连贯性和相对较高的可调试性。关键在于,应用程序处于且仅可能处于有限数量的状态中。然后,某些事件发生会使得应用从一个状态过渡到另一个状态。状态机由触发器驱动,这些触发器基于事件或计时器。
设计高层次逻辑并将其置于应用程序外部,然后通过多种方式与状态机交互,这种方式要简单得多。可以通过发送事件、监听状态机的行为或请求当前状态来与状态机进行交互。
当开发者意识到代码库开始变得般混乱不堪时,就会在现有项目中引入状态机。面条代码表现为无尽的、层级化的IF、ELSE和BREAK子句结构,当事情变得过于复杂时,编译器或许应该建议开发者暂停一下,先休息一下。状态机的引入有助于将复杂多变的应用程序状态转换过程组织得更为有序和清晰,从而避免代码陷入难以维护的境地。
什么是状态
状态是状态机可能处于的一种模型。相比于在通用文档中使用抽象概念,通过现实生活中的例子来描述状态通常更为直观易懂。以一个简单的键盘为例——我们大多数人每天都使用它。如果你有一个标准键盘,左侧有普通键,右侧有数字小键盘,你可能会注意到,根据Numlock(数字锁定)是否激活,数字小键盘可以处于两种不同的状态。如果没有激活,按下数字小键盘的按键会实现方向导航等功能;如果数字小键盘被激活,则按下这些键将输入数字。本质上,键盘的数字小键盘部分可以处于两种不同的状态。
将状态的概念联系到编程上,这意味着我们可以不再依赖于标志位、嵌套的if/else/break语句或其他不切实际(有时甚至是曲折复杂的)逻辑,而是可以通过状态、状态变量或与状态机的交互来处理问题。换句话说,在编程中运用状态这一概念,能够帮助我们更清晰地组织和管理程序的不同状态及其转换过程。
什么是状态机
状态机是一种理论模型,它描述了一个对象在其生命周期内可能经历的有限数量的状态及其之间的转换规则。每个状态都有触发状态迁移的条件(通常是事件),并且可以关联执行的动作。
状态机的核心在于状态变迁和事件驱动,适合处理异步和并发的情况。状态机强调的是系统当前所处的状态,并且关注于系统如何根据接收到的外部事件或内部条件进行状态转变。
状态机最常见于嵌入式系统、用户界面交互设计、游戏开发、网络协议解析等领域。
以下以游戏马里奥的状态切换为例,来理解状态机的使用场景:
与状态设计模式的区别
在面向对象编程中,状态设计模式是一种行为型设计模式,允许对象在其内部状态改变时改变其行为。该模式通过将每一个状态封装成一个类,使得当对象的状态发生改变时,它的行为也随之改变,同时能够使代码更加清晰和模块化。
在状态设计模式中,每个状态是一个单独的类实例,这些类通常会实现一个公共接口,以便上下文对象可以调用适当的方法,而无需知道具体当前处于哪种状态。上下文对象(context)持有对当前状态对象的引用,并在接收到特定事件时调用状态对象的方法来处理事件并可能导致状态切换。
联系:
- 状态设计模式是对状态机理论的一种实现,它把状态机的概念应用于软件设计中,利用面向对象技术实现了状态的抽象、封装和扩展性。
区别:
- 状态机是一个抽象的概念,可以不依赖于任何特定的编程语言或设计模式独立存在。
- 状态设计模式则是具体的编程实践,是针对解决状态转换问题的一种设计解决方案,特别适用于面向对象环境下的复杂状态管理。
与流程引擎的区别
流程引擎(Business Process Management Engine, BPMN Engine)是实现业务流程管理(BPM)的软件组件,主要用于执行和监控预定义的工作流程。这些工作流程通常包括一系列顺序执行的任务或活动,具有明确的开始点、结束点和中间过程。
流程引擎支持更复杂的流程结构,如并行分支、同步合并、循环等,并提供了丰富的建模语言(如BPMN)来可视化表示流程逻辑。流程引擎不仅关注状态转移,还注重任务分配、资源调度、事务处理以及流程实例的整体生命周期管理。
流程引擎适用于企业级应用中需要自动化、规范化和优化的复杂业务流程,比如采购审批流程、贷款审批流程、订单处理流程等。
区别与联系:
- 目的性不同: 状态机主要解决状态变化的问题,而流程引擎则更多地关注流程的整体组织和执行。
- 结构灵活性: 状态机结构相对简单,特别适合清晰、固定的流程;流程引擎支持多层次、多路径的复杂流程,允许动态调整和扩展。
- 参与角色: 状态机侧重于机器层面的自动化处理,流程引擎则常涉及人的参与决策和协同工作。
- 集成度: 流程引擎通常包含更多的管理和监控功能,能够与组织其他系统紧密集成,提供强大的审计跟踪、异常处理和数据分析能力。
- 联系: 在实际项目中,状态机的概念和机制可能会作为流程引擎的一部分被采用,尤其是在流程中有明显的状态变迁环节时。同时,两者都可以用作业务规则和流程规范的有效工具,只不过各自聚焦的领域和复杂程度有所差异。
什么是Spring状态机
Spring Statemachine(SSM)是一个框架,允许应用程序开发者在Spring应用中使用传统的状态机概念。SSM提供了以下功能:
- 为简单用例提供易于使用的单层(一级)状态机。
- 采用分层状态机结构,便于配置复杂状态。
- 状态机区域以支持更为复杂的状态配置。
- 支持触发器、转换、守卫和动作的使用。
- 提供类型安全的配置适配器。
- 集成了状态机事件监听器。
- 与Spring IoC(控制反转)集成,可将Bean关联至状态机。
SSM有哪些使用场景
项目适于使用状态机的场景包括:
- 当你可以将应用程序或其部分结构表示为一系列状态时,该项目是应用状态机的良好候选者。
- 你希望将复杂的逻辑拆分为更小、更易于管理的任务。
- 应用程序已经存在并发问题,例如异步操作导致的问题。
在以下情况下,实际上你已经在尝试实现一个状态机:
- 使用布尔标志或枚举来模拟各种情况。这意味着你的代码可能在通过这些标志和枚举跟踪不同状态。
- 拥有仅在应用程序生命周期中的某些阶段才有意义的变量。这暗示了状态变化对程序流程的影响。
- 正在循环遍历if-else结构(或者更糟糕的是,多个这样的结构),检查特定标志或枚举是否已设置,然后根据这些标志和枚举是否存在及其组合进一步判断接下来的操作。这种编程方式本质上是在手动处理状态转移,而采用状态机可以更清晰、规范地表述并简化此类复杂的状态转换逻辑。
如何集成SSM
需要在maven或者gradle中ssm的依赖。
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>4.0.0</version>
</dependency>
implementation 'org.springframework.statemachine:spring-statemachine-starter:4.0.0'
如何使用SSM
下面以一个简单的例子来说明如何使用SSM。
// 定义对应状态和事件的枚举:
public enum States {
SI, S1, S2
}
public enum Events {
E1, E2
}
// 定义状态机的配置
import java.util.EnumSet;
@Configuration // 标识为配置类
@EnableStateMachine // 启用状态机功能
public class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
/**
* 配置状态机的全局属性,如自动启动和状态监听器。
*
* @param config 状态机配置构建器
* @throws Exception 如果配置过程中发生错误
*/
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true) // 设置状态机自动启动
.listener(listener()); // 注册状态改变监听器
}
/**
* 配置状态机的状态。
*
* @param states 状态配置构建器
* @throws Exception 如果配置过程中发生错误
*/
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.SI) // 设置初始状态为SI
.states(EnumSet.allOf(States.class)); // 将所有枚举状态添加到状态机
}
/**
* 配置状态机的转换。
*
* @param transitions 转换配置构建器
* @throws Exception 如果配置过程中发生错误
*/
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal() // 配置外部触发的转换
.source(States.SI).target(States.S1).event(Events.E1) // 定义从SI到S1的转换,由事件E1触发
.and() // 连接另一个转换配置
.withExternal() // 另一个外部触发的转换
.source(States.S1).target(States.S2).event(Events.E2); // 定义从S1到S2的转换,由事件E2触发
}
/**
* 创建并返回一个状态机监听器,用于监听状态的改变。
*
* @return 状态机监听器实例
*/
@Bean
public StateMachineListener<States, Events> listener() {
return new StateMachineListenerAdapter<States, Events>() {
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
if(from != null){
System.out.println("State change from " + from.getId());
}
System.out.println("State change to " + to.getId());
}
};
}
}
测试代码如下:
@RestController
@Tag(name = "状态机", description = "状态机")
public class StateController{
@Autowired
private StateService stateService;
@GetMapping(value = "改变状态")
@Operation(description = "改变状态")
public void change() {
stateService.changeState();
}
}
/**
* 状态机演示服务
*/
@Service
public class StateService {
@Autowired
private StateMachine<States, Events> stateMachine;
public void changeState() {
stateMachine.sendEvent(Events.E1);
stateMachine.sendEvent(Events.E2);
}
}
服务层的输出的结果如下:
State change to SI
State change from SI
State change to S1
State change from S1
State change to S2
以上代码只是简单演示了SSM的集成和使用demo。实际业务场景可能更为复杂,需要根据实际需求进行扩展。
除了状态,要更好的使用SSM还需要理解伪状态等很多概念,比如Junction(允许多个传入转换)、 Fork(一个或多个区域的显式入口)、Join (将源自不同区域的多个过渡合并在一起)。这部分内容将在后续文章中进行介绍。
参考
- A State Machine Crash Course - spring-statemachine https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#crashcourse
关于作者
来自一线全栈程序员nine的八年探索与实践,持续迭代中。欢迎关注公众号“雨林寻北”或添加个人卫星codetrend(备注技术)。