????????Spring State Machine 是一个用于处理状态机逻辑的框架,它提供了一种简洁的方法来定义状
态、转换以及在状态变更时触发的动作。
状态?(?State?)?:一个状态机至少要包含两个状态。例如自动门的例子,有 open 和 closed 两个状态。
事件?(?Event?)??:事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。
动作?(?Action?)??:事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action一般就对应一个函数。
转换?(?Transition?)?:也就是从一个状态变化为另一个状态。例如“开门过程”就是一个转换。
守卫(Guard)?:一种条件逻辑,用于决定是否可以进行某个状态转换。守卫可以基于应用程序的当前状态或其他条件来确定转换是否应该发生。
????????有限状态机(Finite-state machine,FSM),又称有限状态自动机,简称状态机,是表示有限
个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想,简单而言,有
限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组
成。其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事
件。
官方地址:
文档地址
Spring Statemachine - Reference Documentation
github地址
https://github.com/spring-projects/spring-statemachine
????????做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,
我们就可以完成一个状态机图了,以订单为例:通过支付事件,订单状态从待支付状态转换为待发货状态。
注意事项:
<!--spring 状态机start-->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>${spring-statemachine.version}</version>
</dependency>
<!-- redis持久化状态机 -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-data-redis</artifactId>
<version>${spring-statemachine.version}</version>
</dependency>
<!--spring 状态机end-->
<!--redis start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis end-->
package com.ybw.constant;
/**
* 状态
*
* @author weixiansheng
* @version V1.0
* @className RegStatusEnum
* @date 2023/12/26
**/
public enum RegStatusEnum {
// 未连接
UNCONNECTED,
// 已连接
CONNECTED,
// 注册中
REGISTERING,
// 已注册
REGISTERED;
}
package com.ybw.constant;
/**
* 事件
*
* @author weixiansheng
* @version V1.0
* @className RegEventEnum
* @date 2023/12/26
**/
public enum RegEventEnum {
// 连接
CONNECT,
// 注册
REGISTER,
// 注册成功
REGISTER_SUCCESS,
// 注册失败
REGISTER_FAILED,
// 注销
UN_REGISTER;
}
创建一个配置类来配置状态机。在这个配置中,我们定义状态转换逻辑。
package com.ybw.config;
import com.ybw.constant.RegEventEnum;
import com.ybw.constant.RegStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import java.util.EnumSet;
/**
* @author weixiansheng
* @version V1.0
* @className StateMachineConfig
* @date 2023/12/25
**/
@Configuration
@EnableStateMachine
@Slf4j
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
/**
* 配置状态
*
* @param states
* @methodName: configure
* @return: void
* @author: weixiansheng
* @date: 2023/12/25
**/
@Override
public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
states.withStates()
// 定义初始状态
.initial(RegStatusEnum.UNCONNECTED)
// 定义状态机状态
.states(EnumSet.allOf(RegStatusEnum.class));
}
/**
* 配置状态转换事件关系
*
* @param transitions
* @methodName: configure
* @return: void
* @author: weixiansheng
* @date: 2023/12/25
**/
@Override
public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions) throws Exception {
transitions
// 1.连接事件
// 未连接 -> 已连接
.withExternal().source(RegStatusEnum.UNCONNECTED).target(RegStatusEnum.CONNECTED).event(RegEventEnum.CONNECT).and()
// 2.注册事件
// 已连接 -> 注册中
.withExternal().source(RegStatusEnum.CONNECTED).target(RegStatusEnum.REGISTERING).event(RegEventEnum.REGISTER).and()
// 3.注册成功事件
// 注册中 -> 已注册
.withExternal().source(RegStatusEnum.REGISTERING).target(RegStatusEnum.REGISTERED).event(RegEventEnum.REGISTER_SUCCESS).and()
// 5.注销事件
// 已连接 -> 未连接
.withExternal().source(RegStatusEnum.CONNECTED).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER).and()
// 注册中 -> 未连接
.withExternal().source(RegStatusEnum.REGISTERING).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER).and()
// 已注册 -> 未连接
.withExternal().source(RegStatusEnum.REGISTERED).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER);
}
}
package com.ybw.config;
import com.alibaba.fastjson2.JSON;
import com.ybw.constant.RegEventEnum;
import com.ybw.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
/**
* 状态机的转换事件配置
*
* @author weixiansheng
* @version V1.0
* @className StateMachineEventConfig
* @date 2023/12/26
**/
@WithStateMachine
@Slf4j
public class StateMachineEventConfig {
/**
* 连接事件
*
* @param message
* @methodName: connect
* @return: void
* @author: weixiansheng
* @date: 2023/12/26
**/
@OnTransition(source = "UNCONNECTED", target = "CONNECTED")
public void connect(Message<RegEventEnum> message) {
Order order = (Order) message.getHeaders().get("order");
log.info("///");
log.info("连接事件, 未连接 -> 已连接,order:{}", JSON.toJSONString(order));
log.info("///");
}
/**
* 注册事件
*
* @param message
* @methodName: register
* @return: void
* @author: weixiansheng
* @date: 2023/12/26
**/
@OnTransition(source = "CONNECTED", target = "REGISTERING")
public void register(Message<RegEventEnum> message) {
log.info("///");
log.info("注册事件, 已连接 -> 注册中");
log.info("///");
}
/**
* 注册成功事件
*
* @param message
* @methodName: registerSuccess
* @return: void
* @author: weixiansheng
* @date: 2023/12/26
**/
@OnTransition(source = "REGISTERING", target = "REGISTERED")
public void registerSuccess(Message<RegEventEnum> message) {
log.info("///");
log.info("注册成功事件, 注册中 -> 已注册");
log.info("///");
}
/**
* 注销事件
*
* @param message
* @methodName: unRegister
* @return: void
* @author: weixiansheng
* @date: 2023/12/26
**/
@OnTransition(source = "REGISTERED", target = "UNCONNECTED")
public void unRegister(Message<RegEventEnum> message) {
log.info("///");
log.info("注销事件, 已注册 -> 未连接");
log.info("///");
}
}
持久化到redis中,在分布式系统中使用。
package com.ybw.config;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.statemachine.data.redis.RedisStateMachineContextRepository;
import org.springframework.statemachine.data.redis.RedisStateMachinePersister;
import org.springframework.statemachine.persist.RepositoryStateMachinePersist;
/**
* 持久化
*
* @author weixiansheng
* @version V1.0
* @className Persist
* @date 2023/12/26
**/
@Configuration
@Slf4j
public class Persist<E, S> {
@Resource
private RedisConnectionFactory redisConnectionFactory;
/**
* 持久化到redis中,在分布式系统中使用
*
* @methodName: getRedisPersister
* @return: org.springframework.statemachine.data.redis.RedisStateMachinePersister<E, S>
* @author: weixiansheng
* @date: 2023/12/26
**/
@Bean(name = "stateMachineRedisPersister")
public RedisStateMachinePersister<E, S> getRedisPersister() {
RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
RepositoryStateMachinePersist<E, S> p = new RepositoryStateMachinePersist<>(repository);
return new RedisStateMachinePersister<>(p);
}
}
package com.ybw.state;
import com.ybw.constant.RegEventEnum;
import com.ybw.constant.RegStatusEnum;
import com.ybw.entity.Order;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.data.redis.RedisStateMachinePersister;
import org.springframework.statemachine.persist.StateMachinePersister;
/**
* @author weixiansheng
* @version V1.0
* @className StateTest
* @date 2023/12/26
**/
@SpringBootTest
@Slf4j
public class StateTest {
@Resource
private StateMachine<RegStatusEnum, RegEventEnum> stateMachine;
@Resource(name = "stateMachineRedisPersister")
private RedisStateMachinePersister<RegStatusEnum, RegEventEnum> persister;
/**
* @MethodName: testState
* @Description:
* @Param: []
* @Return: void
* @Author: ybwei
* @Date: 2020/3/26
**/
@Test
public void testState() {
try {
Order order = Order.builder()
.id(1L)
.name("张三")
.status(RegStatusEnum.CONNECTED)
.build();
stateMachine.start();
//尝试恢复状态机状态(read)
persister.restore(stateMachine, order.getId().toString());
Message<RegEventEnum> message = MessageBuilder.withPayload(RegEventEnum.CONNECT).setHeader("order", order).build();
stateMachine.sendEvent(message);
//持久化状态机状态(write)
persister.persist(stateMachine, order.getId().toString());
} catch (Exception e) {
log.error("testState error:", e);
}
// stateMachine.sendEvent(RegEventEnum.REGISTER);
// stateMachine.sendEvent(RegEventEnum.REGISTER_SUCCESS);
// stateMachine.sendEvent(RegEventEnum.UN_REGISTER);
//
// stateMachine.sendEvent(RegEventEnum.CONNECT);
}
}
?执行结果:
第一次执行
[INFO ] 2023-12-26 17:35:20.563 [main] o.s.s.support.AbstractStateMachine - Got null context, resetting to initial state, clearing extended state and machine id
[INFO ] 2023-12-26 17:35:20.593 [main] c.ybw.config.StateMachineEventConfig - ///
[INFO ] 2023-12-26 17:35:20.688 [main] c.ybw.config.StateMachineEventConfig - 连接事件, 未连接 -> 已连接,order:{"id":1,"name":"张三","status":"CONNECTED"}
[INFO ] 2023-12-26 17:35:20.690 [main] c.ybw.config.StateMachineEventConfig - ///
第二次执行,没有日志打印。
修改代码
将RegEventEnum.CONNECT改为RegEventEnum.REGISTER
@Test
public void testState() {
try {
Order order = Order.builder()
.id(1L)
.name("张三")
.status(RegStatusEnum.CONNECTED)
.build();
stateMachine.start();
//尝试恢复状态机状态(read)
persister.restore(stateMachine, order.getId().toString());
Message<RegEventEnum> message = MessageBuilder.withPayload(RegEventEnum.REGISTER).setHeader("order", order).build();
stateMachine.sendEvent(message);
//持久化状态机状态(write)
persister.persist(stateMachine, order.getId().toString());
} catch (Exception e) {
log.error("testState error:", e);
}
}
第三次执行,打印日志
[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - ///
[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - 注册事件, 已连接 -> 注册中
[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - ///
?总结: