介绍
Spring Statemachine(SSM)是一个框架,供应用程序开发人员在Spring应用程序中使用传统的状态机概念。SSM旨在提供以下功能:
- 易于使用的单层状态机,用于简单的用例。
- 分层状态机结构可简化复杂的状态配置。
- 状态机区域提供甚至更复杂的状态配置。
- 触发器,过渡,防护和操作的使用。
- 键入安全配置适配器。
- 状态机事件监听器。
- Spring IOC集成,将Bean与状态机关联。
状态机之所以强大,是因为始终保证行为是一致的,因此调试起来相对容易。这是因为在启动机器时,操作规则是一成不变的。想法是,您的应用程序可能以有限数量的状态存在,并且某些预定义的触发器可以将您的应用程序从一种状态转移到另一种状态。这样的触发器可以基于事件或计时器。在应用程序外部定义高级逻辑,然后依靠状态机来管理状态要容易得多。您可以通过发送事件,侦听更改或仅请求当前状态来与状态机进行交互。
基础概念
先从状态机的定义入手,StateMachine,其中:
- StateMachine:状态机模型
- state:S-状态,一般定义为一个枚举类,如创建、待风控审核、待支付等状态
- event:E-事件,同样定义成一个枚举类,如订单创建、订单审核、支付等,代表一个动作。
一个状态机的定义就由这两个主要的元素组成,状态及对对应的事件(动作)
状态机-相关概念
- Transition: 节点,是组成状态机引擎的核心
- source:节点的当前状态
- target:节点的目标状态
- event:触发节点从当前状态到目标状态的动作
- guard:起校验功能,一般用于校验是否可以执行后续action
- action:用于实现当前节点对应的业务逻辑处理
- withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
- withInternal 我们支持三种不同类型的转换
external
,internal
和local
。转换是通过信号触发的,该信号是发送到状态机的事件或计时器。
@Configuration @EnableStateMachine public class Config1Enums extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) // 自定义初始状态枚举 .end(States.SF) //最终状态枚举。可以多个
.choice(States.SC)//一个伪状态,表示1:N子状态
.states(EnumSet.allOf(States.class));
} }
框架搭建
1.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>test-project-demo</artifactId> <groupId>com.zero.x</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-statemachine-demo</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>2.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.74</version> </dependency> </dependencies> </project>
2.配置类
package com.zero.x.config; import com.alibaba.fastjson.JSON; import com.zero.x.entity.OrderDTO; import com.zero.x.enums.Events; import com.zero.x.enums.States; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.StateMachinePersist; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.persist.DefaultStateMachinePersister; import org.springframework.statemachine.persist.StateMachinePersister; import org.springframework.statemachine.support.DefaultStateMachineContext; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @author admin */ @Configuration @EnableStateMachineFactory public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { /** * 参数key */ public static final String NODE_STATUS_PARAM_NAME = "machine_status_param_key"; private Logger logger = LoggerFactory.getLogger(getClass()); /** * 方法用来初始化当前状态机拥有哪些状态 * * @param states 当前状态机状态对象 * @throws Exception 异常 */ @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates() .initial(States.SF1) // .choice(States.SF3) .states(EnumSet.allOf(States.class)); } /** * 状态事件绑定 * * @param transitions 事件 * @throws Exception */ @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { toForwardFlow(transitions); } /** * 正向流程 * * @param transitions 状态机事件绑定对象 * @throws Exception 异常 */ private void toForwardFlow(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { //正向流程 transitions
.withExternal().source(States.SF1).target(States.SF2).event(Events.idb_1).and() .withExternal().source(States.SF2).target(States.SF3).active(activeExcute()).event(Events.ibd_2).and() // .withChoice() // .source(States.SF3) // .first(States.SF4,guardTo5(),to5()) // .then(States.SF5).and()
// .withExternal().source(States.SF6).target(States.SUCCESS).event(Events.ibd_success); } /** * 判断是否可以执行action动作 * @return Guard<States, Events> */ @Bean public Guard<States, Events> guardTo5() { return context -> { //这里是从请求参数中获取当前节点状态 return guradData(context, "Guard: SF2 ------>SF4",States.SF6); // return true; }; } /** * demo * @param context * @param s * @param states * @return */ private boolean guradData(StateContext<States, Events> context, String s,States states) { String nowNodeStatus = (String) //请求参数带过来的值 context.getMessageHeader(NODE_STATUS_PARAM_NAME); if (states.name().equals(nowNodeStatus)) { logger.info(s); return true; } return false; } public Action<States, Events> to5() { return context -> { //这里是从请求参数中获取当前节点状态 logger.info("F5:"); // 订单创建相关请求 String request = (String) context.getMessageHeader("param_key"); // 从context中获取状态机 StateMachine<States, Events> stateMachine = context.getStateMachine(); if(Objects.nonNull(stateMachine)){ logger.info("转入F5分 : order info={},stateMachine id={},uuid={},jump from {} to sign status", request, stateMachine.getId(), stateMachine.getUuid(), stateMachine.getState().getId()); } //todo 处理业务流程 }; }
说明:
.choice(States.SF3): 表示这个状态是一个虚拟状态,不会有事件发生,与下面withChoice()配套使用。这里如果不做声明配置就会报错。
.withChoice().source(States.SF3):表示状态机状态扭转到 SF3 状态时,会走一个选择。 SF3在这里不能在source()后面接事件绑定,SF3相当于是一个后面状态机扭转到 SF5 还是 SF4 基础条件。是一个虚拟节点状态。及状态机状态是:SF2->SF4 或者 SF2->SF5状态。 SF3 是一个 SF2 过度到 SF4 和 SF5 的一个虚拟状态。
.first().then().last(): 相当于 if/else if/else。注意一定要配置 last()。可以不要then()。
Guard<States, Events> guardTo5():判断条件,相当于if(判断条件) ,当只有这个方法返回是 true,才会执行对应的 action 方法。
Action<States, Events> to5()/activeExceute()(可以选择配置):这个方法是指状态机达到 SF3 状态时,才会触发这个方法,表示当前状态可以做的行为。后面有更会优美的写法。
public void configure(StateMachineConfigurationConfigurer<States, Events> config)(可选配置): 配置监听器,与 listener() 一起工作。
listener()(可选配置):监听器,主要针对状态变更时会触发这里。注意一下,这个方法提供的是当前状态 .getTarget() 及目标的状态,初始状态没有目标状态(start的时候),这里会有点问题。
注意事项:
@EnableStateMachineFactory: 这是表示是一个工厂,每次都会在从这个工厂中获取新的StateMachine。(生产环境)
@EnableStateMachine: 表示单例,及每次获取都是同一个StateMachine。原因是StateMachine是一个有状态对象,不是一个无状态的对象,使用单例在会使状态乱序。
3.使用
@Resource
private StateMachineFactory<States, Events> factory;
@Resource
private StateMachinePersister<States, Events, OrderDTO> persister;
public static void main(String[] args) {
//传递参数
Map<String, Object> headers = new HashMap<>();
headers.put(StateMachineConfig.NODE_STATUS_PARAM_NAME, requestParam);
headers.put("param_key", "1123678888");
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(123);
orderDTO.setName("wwww");
sendEvent(headers,orderDTO,States.SF4, Events.testActive);
}
/**
* 状态机发送消息
* @param param 参数
* @param orderDTO 实体类
* @param state 当前状态
* @param event 当前状态执行的事件
*/
public void sendEvent(Map<String,Object> param, OrderDTO orderDTO,States state,Events event){
try {
//从工厂中获取stateMachine
StateMachine<States, Events> stateMachine = factory.getStateMachine();
//重置状态机
persister.restore(stateMachine,orderDTO);
//构建消息体
Message<Events> message = builderEventsMessage(event,param);
//向状态机发送消息
stateMachine.sendEvent(message);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 构建事件的消息体
*
* @param events 事件
* @param requestParam 参数
* @return Message<Events>
*/
private Message<Events> builderEventsMessage(Events events, Map<String,Object> headers) {
return new Message<Events>() {
@Override
public Events getPayload() {
//执行的事件
return events;
}
@Override
public MessageHeaders getHeaders() {
//消息传递
return new MessageHeaders(headers);
}
};
}
Action: 状态机执行的行为
public Action<States, Events> activeExceute()() {
return context -> {
//这里是从请求参数中获取当前节点状态
logger.info("F5:");
// 订单创建相关请求
String request = (String) context.getMessageHeader("param_key");
// 从context中获取状态机
StateMachine<States, Events> stateMachine = context.getStateMachine();
if(Objects.nonNull(stateMachine)){
logger.info("转入F5分 : order info={},stateMachine id={},uuid={},jump from {} to sign status",
request,
stateMachine.getId(),
stateMachine.getUuid(),
stateMachine.getState().getId());
}
//todo 处理业务流程
};
}
总结:
状态机是通过 源状态(source)+ 事件(event)达到 目标状态(target )。基础版本其实就这样可以了。
引用相关文章:
https://juejin.im/post/6844903951154806798
https://docs.spring.io/spring-statemachine/docs/2.0.2.RELEASE/reference/htmlsingle/#glossary