有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。将状态和事件控制从不同的业务Service方法的if else中抽离出来。FSM的应用范围很广,对于有复杂状态流,扩展性要求比较高的场景都可以使用该模型。下面是状态机模型中的4个要素,即现态、条件、动作、次态。
现态:是指当前所处的状态。
条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
状态机对于流程性、状态变化的场景,它就是一个清晰的表达方式,这就是它的好处。
使用maven项目,首先引入包
pom.xml
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.statemachine
spring-statemachine-core
1.2.0.RELEASE
配置文件
StateMachineConfig.java
package com.huaquan.securityguard.config;
import com.huaquan.securityguard.enums.EventEnum;
import com.huaquan.securityguard.enums.StatusEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
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 java.util.EnumSet;
/**
* @author 陆杰
* @since 0.1
*/
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter {
/**
* 初始化状态机状态
*/
@Override
public void configure(StateMachineStateConfigurer states) throws Exception {
states.withStates()
// 定义初始状态
.initial(StatusEnum.UNCONNECTED)
// 定义状态机状态
.states(EnumSet.allOf(StatusEnum.class));
}
/**
* 初始化状态迁移事件
*/
@Override
public void configure(StateMachineTransitionConfigurer transitions)
throws Exception {
transitions
// 1.连接事件
// 未连接 -> 已连接
.withExternal()
.source(StatusEnum.UNCONNECTED)
.target(StatusEnum.START)
.event(EventEnum.CONNECT)
.and()
// 2.更新事件
// 已连接 -> 更新中
.withExternal()
.source(StatusEnum.START)
.target(StatusEnum.UPDATE)
.event(EventEnum.UPDATE)
.and()
// 更新过期人员
.withExternal()
.source(StatusEnum.START)
.target(StatusEnum.UPDATE_EXPER)
.event(EventEnum.UPDATE_EXPER)
.and()
.withExternal()
.source(StatusEnum.UPDATE_EXPER)
.target(StatusEnum.END)
.event(EventEnum.UPDATE_SUCCESS)
.and()
// 3.更新成功事件
//更新中 -> 已完成
.withExternal()
.source(StatusEnum.UPDATE)
.target(StatusEnum.END)
.event(EventEnum.UPDATE_SUCCESS)
.and()
// 5.结束事件
// 已完成 -> 未连接
.withExternal()
.source(StatusEnum.END)
.target(StatusEnum.UNCONNECTED)
.event(EventEnum.END)
;
}
}
StateMachineEventConfig.java
package com.huaquan.securityguard.config;
import com.huaquan.securityguard.service.ISqliteService;
import com.huaquan.securityguard.service.impl.InfoService;
import com.huaquan.securityguard.util.IApiUtil;
import com.huaquan.securityguard.util.ISeverUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
/**
* 功能:状态机
* 2019年6月22日 v0.1
*
* @author 陆杰
* @since 0.1
*/
@WithStateMachine
public class StateMachineEventConfig {
Logger logger = LoggerFactory.getLogger(InfoService.class);
@Autowired
private ISqliteService iSqliteService;
@Autowired
private InfoService infoService;
@Autowired
private ISeverUtil iSeverUtil;
@Autowired
private IApiUtil iApiUtil;
@OnTransition(source = "UNCONNECTED", target = "START")
public void connect() {
iApiUtil.login();
logger.info("连接事件, 未连接 -> 已连接");
}
@OnTransition(source = "START", target = "UPDATE")
public void update() throws InterruptedException {
logger.info("UpdateIncrease----");
// iSqliteService.insertLogViewWithStartTime(iSeverUtil.timeLtoS(System.currentTimeMillis()));
infoService.UpdateTheFailMessage();
infoService.UpdateFromRemoteDB();
Thread.sleep(2000);
}
@OnTransition(source = "START", target = "UPDATE_EXPER")
public void upExperson() throws InterruptedException {
logger.info("UpdateEx----");
infoService.UpdateExPerson();
Thread.sleep(2000);
}
@OnTransition(source = "UPDATE", target = "END")
public void updateSuccess() {
logger.info("update success!");
}
@OnTransition(source = "END", target = "UNCONNECTED")
public void unConnectSuccess() {
logger.info("connect cancel");
}
}
enums 枚举,状态机中表示状态
EventEnum.java
public enum EventEnum {
//未连接
UNCONNECTED,
// 连接
CONNECT,
// 更新
UPDATE,
// 更新成功
UPDATE_SUCCESS,
// 更新过期人员
UPDATE_EXPER,
// 更新失败
UPDATE_FAILED,
// 注销
END;
}
StatusEnum.java
public enum StatusEnum {
// 未连接
UNCONNECTED,
//开始
START,
// 更新中
UPDATE,
// 更新过期人员
UPDATE_EXPER,
// 结束
END,
}
启动状态机
@Autowired
private StateMachine stateMachine;//引入状态机
stateMachine.start();
stateMachine.sendEvent(EventEnum.CONNECT);
stateMachine.sendEvent(EventEnum.UPDATE);
stateMachine.sendEvent(EventEnum.UPDATE_SUCCESS);
stateMachine.sendEvent(EventEnum.END);
stateMachine.sendEvent(EventEnum.UNCONNECTED);
(1)在实际使用中,应该是有很多任务的流程在同时跑,而不是像这个例子,全部任务共用一个流程,一个订单到EventEnum.UNCONNECTED状态了,其他任务就不能是UPDATE状态了。
(2)参数问题,我们做项目,不是为了看日志打出“---订单创建,待支付---”给我们玩的,而是要处理具体业务的,拿订单流程来说吧,订单号怎么传,状态改变时间怎么回数据库,等等问题其实都需要解决。
(3)存储问题,状态机如果有多个,需要的时候从哪去,暂时不需要了放哪去,这都是问题,所以存储状态机也是需要解决的。
在实际项目中一般性都会有多个状态机并发执行,在上面的例子中在程序中同时执行只能同时存在一个状态机,如果想要多个状态机并发就需要用到builer
StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
OrderStateMachineBuilder.java
private final static String MACHINEID = "orderMachine";
public StateMachine build(BeanFactory beanFactory) throws Exception {
StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
System.out.println("构建订单状态机");
builder.configureConfiguration()
.withConfiguration()
.machineId(MACHINEID)
.beanFactory(beanFactory);
builder.configureStates()
.withStates()
.initial(OrderStates.UNPAID)
.states(EnumSet.allOf(OrderStates.class));
builder.configureTransitions()
.withExternal()
.source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_RECEIVE)
.event(OrderEvents.PAY).action(action())
.and()
.withExternal()
.source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE)
.event(OrderEvents.RECEIVE);
return builder.build();
}
其中MACHINEID指向EventConfig
private final static String MACHINEID = "orderMachine";
builder.configureConfiguration()
.withConfiguration()
.machineId(MACHINEID)
.beanFactory(beanFactory);
当然为了能调用到EventConfig,需要在EventConfig中注明它的名字,在1.x版本中不存在id这个字段,所以是无法关联的
@WithStateMachine(id="orderMachine")
在WithStateMachine中有两个字段
public @interface WithStateMachine {
String name() default "stateMachine";
String id() default "";
}
OrderEventConfig.java
@WithStateMachine(id="orderMachine")
public class OrderEventConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 当前状态UNPAID
*/
@OnTransition(target = "UNPAID")
public void create() {
logger.info("---订单创建,待支付---");
}
/**
* UNPAID->WAITING_FOR_RECEIVE 执行的动作
*/
@OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void pay(Message message) {
System.out.println("传递的参数:" + message.getHeaders().get("order"));
logger.info("---用户完成支付,待收货---");
}
/**
* WAITING_FOR_RECEIVE->DONE 执行的动作
*/
@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive(Message message) {
System.out.println("传递的参数:" + message.getHeaders().get("order"));
System.out.println("传递的参数:" + message.getHeaders().get("otherObj"));
logger.info("---用户已收货,订单完成---");
}
}
调用状态机代码,在调用状态机时候,现在每一次调用都会新建一个状态机并发运行。
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
System.out.println(stateMachine.getId());
// 创建流程
stateMachine.start();
// 触发PAY事件
stateMachine.sendEvent(OrderEvents.PAY);
// 触发RECEIVE事件
//stateMachine.sendEvent(OrderEvents.RECEIVE);
//用message传递数据
Order order = new Order(orderId, String.valueOf(Math.random()), "华师大科技园", "13435465465", "RECEIVE");
Message message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherObj", "otherObjValue").build();
stateMachine.sendEvent(message);
// 获取最终状态
System.out.println("最终状态:" + stateMachine.getState().getId());
调用结果,因为这里的状态不是一个循环,如果不是并发创建新的状态机是无法第二次执行打印状态机执行的内容的。
2020-05-13 10:50:04.645 INFO 12176 --- [nio-9991-exec-1] tConfig$$EnhancerBySpringCGLIB$$928f5e35 : ---用户完成支付,待收货---
传递的参数:Order [id=null, userId=0.6601078768353066, address=华师大科技园, phoneNum=13435465465, state=RECEIVE]
传递的参数:otherObjValue
2020-05-13 10:50:04.648 INFO 12176 --- [nio-9991-exec-1] tConfig$$EnhancerBySpringCGLIB$$928f5e35 : ---用户已收货,订单完成---
最终状态:DONE
构建订单状态机
orderMachine
2020-05-13 10:50:09.395 INFO 12176 --- [nio-9991-exec-2] tConfig$$EnhancerBySpringCGLIB$$928f5e35 : ---订单创建,待支付---
2020-05-13 10:50:09.396 INFO 12176 --- [nio-9991-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@321218cf
2020-05-13 10:50:09.396 INFO 12176 --- [nio-9991-exec-2] o.s.s.support.LifecycleObjectSupport : started DONE UNPAID WAITING_FOR_RECEIVE / UNPAID / uuid=e9b620a5-cfe1-4018-b0d0-f5c14046f760 / id=orderMachine
DefaultStateContext [stage=TRANSITION, message=GenericMessage [payload=PAY, headers={id=36d3ed81-88ce-8524-db84-a791a258b891, timestamp=1589338209396}], messageHeaders={id=5cb640ed-3d05-972e-a291-eb2a66377b66, _sm_id_=e9b620a5-cfe1-4018-b0d0-f5c14046f760, timestamp=1589338209396}, extendedState=DefaultExtendedState [variables={}], transition=AbstractTransition [source=ObjectState [getIds()=[UNPAID], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=1159188520, toString()=AbstractState [id=UNPAID, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@1546ee, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]], target=ObjectState [getIds()=[WAITING_FOR_RECEIVE], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=836740284, toString()=AbstractState [id=WAITING_FOR_RECEIVE, pseudoState=null, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]], kind=EXTERNAL, guard=null], stateMachine=DONE UNPAID WAITING_FOR_RECEIVE / UNPAID / uuid=e9b620a5-cfe1-4018-b0d0-f5c14046f760 / id=orderMachine, source=null, target=null, sources=null, targets=null, exception=null]
传递的参数:null
2020-05-13 10:50:09.397 INFO 12176 --- [nio-9991-exec-2] tConfig$$EnhancerBySpringCGLIB$$928f5e35 : ---用户完成支付,待收货---
传递的参数:Order [id=null, userId=0.5339466950886407, address=华师大科技园, phoneNum=13435465465, state=RECEIVE]
传递的参数:otherObjValue
2020-05-13 10:50:09.397 INFO 12176 --- [nio-9991-exec-2] tConfig$$EnhancerBySpringCGLIB$$928f5e35 : ---用户已收货,订单完成---
最终状态:DONE
在实际需求中,一个程序可能有不同种类的状态机,服务不同的需求所以往往需要多个状态机,在实际操作中,我们只需要把创建状态机的步骤再做一遍取不同的名字就可以在一个程序内创建不同的状态机了。
在builder中根据需求各自创建config
private final static String MACHINEID = "orderMachine";
public StateMachine build(BeanFactory beanFactory) throws Exception {
StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
System.out.println("构建订单状态机");
builder.configureConfiguration()
.withConfiguration()
.machineId(MACHINEID)
.beanFactory(beanFactory);
...省略
private final static String MACHINEID = "formMachine";
public StateMachine build(BeanFactory beanFactory) throws Exception {
StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
System.out.println("构建表单状态机");
builder.configureConfiguration()
.withConfiguration()
.machineId(MACHINEID)
.beanFactory(beanFactory);
...省略
各自声明对应的eventConfig
@WithStateMachine(id="orderMachine")
public class OrderEventConfig {
...省略
@WithStateMachine(id="formMachine")
public class FormEventConfig {
分别执行不同的状态机
@RequestMapping("/testOrderState")
public void testOrderState(String orderId) throws Exception {
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
System.out.println(stateMachine.getId());
// 创建流程
stateMachine.start();
// 触发PAY事件
stateMachine.sendEvent(OrderEvents.PAY);
// 触发RECEIVE事件
stateMachine.sendEvent(OrderEvents.RECEIVE);
// 获取最终状态
System.out.println("最终状态:" + stateMachine.getState().getId());
}
@RequestMapping("/testFormState")
public void testFormState() throws Exception {
StateMachine stateMachine = formStateMachineBuilder.build(beanFactory);
System.out.println(stateMachine.getId());
// 创建流程
stateMachine.start();
stateMachine.sendEvent(FormEvents.WRITE);
stateMachine.sendEvent(FormEvents.CONFIRM);
stateMachine.sendEvent(FormEvents.SUBMIT);
// 获取最终状态
System.out.println("最终状态:" + stateMachine.getState().getId());
}
在企业开发中,数据在不同的业务间传输是最常见的工作,所以虽然我们的主架构是用的状态机,也就是从流程状态的角度来看待这个项目,但在具体业务中,每个状态的转变中会牵涉到各类业务,这些业务有些需要收到状态机变化的通知,需要把状态值传递给业务类和业务方法,同样的,在处理状态变化是,也需要获取业务数据,方便不同的业务在同一个状态变化环节做各自的业务,下面我们就讲下这个数据在spring statemachine里面的传递。
通过message传递,message只有两部分,header和payload
public interface Message {
T getPayload();
MessageHeaders getHeaders();
}
传参数,在调用状态机时候,在message中可以传递对象,或者基础数据类型,在payload中装载状态即可
Message message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).setHeader("otherObj", "otherObjValue").build();
stateMachine.sendEvent(message);
与普通不装消息的设置状态对比
stateMachine.sendEvent(OrderEvents.PAY);
在接收端,即EventConfig中,方法中添加参数Message
@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive(Message message) {
System.out.println("传递的参数:" + message.getHeaders().get("order"));
System.out.println("传递的参数:" + message.getHeaders().get("otherObj"));
logger.info("---用户已收货,订单完成---");
}
持久化操作在实际业务中经常遇到,有时候需要对一个任务隔天处理,或者再久一点等到需要的时候再拿出来,这时候就要用到持久化操作。
用唯一id作为key,把状态机保存到map表里面,在实际的业务中,自由存取
public class MachineMap {
public static Map> orderMap = new HashMap>();
public static Map> formMap = new HashMap>();
}
关于StateMachinePersist和StateMachinePersister接口:
这两个接口名字很类似,很容易搞混,但下面的是有er的,包名也不同的。StateMachinePersister是可以直接保存StateMachine对象的,对于StateMachinePersist首先要实现它,然后再一个Config类里面转换成下面的StateMachinePersister,转换的代码就在上面的PersistConfig类里。
StateMachinePersist.class和StateMachinePersister.class对比
package org.springframework.statemachine;
public interface StateMachinePersist {
void write(StateMachineContext context, T contextObj) throws Exception;
StateMachineContext read(T contextObj) throws Exception;
}
package org.springframework.statemachine.persist;
import org.springframework.statemachine.StateMachine;
public interface StateMachinePersister {
void persist(StateMachine stateMachine, T contextObj) throws Exception;
StateMachine restore(StateMachine stateMachine, T contextObj) throws Exception;
}
上述描述如何使用StateMachinePersist可能有点模糊,以下为具体步骤:
Step1:StateMachinePersist
@Component
public class InMemoryStateMachinePersist implements StateMachinePersist {
private Map> map = new HashMap>();
@Override
public void write(StateMachineContext context, String contextObj) throws Exception {
map.put(contextObj, context);
}
@Override
public StateMachineContext read(String contextObj) throws Exception {
return map.get(contextObj);
}
}
Step2:然后在 PersistConfig 中 注入:InMemoryStateMachinePersist
@Configuration
public class PersistConfig {
@Autowired
private InMemoryStateMachinePersist inMemoryStateMachinePersist;
/**
* 注入StateMachinePersister对象
*
* @return
*/
@Bean(name="orderMemoryPersister")
public StateMachinePersister getPersister() {
return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist);
}
}
Step3: 调用时根据bean注入
@Resource(name="orderMemoryPersister")
private StateMachinePersister orderMemorypersister;
...
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
stateMachine.start();
//发送PAY事件
stateMachine.sendEvent(OrderEvents.PAY);
Order order = new Order();
order.setId(id);
//持久化stateMachine
orderMemorypersister.persist(stateMachine, order.getId());
...
//取数据
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
orderMemorypersister.restore(stateMachine, id);
System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
6.2持久化到redis
pom.xml 依赖
org.springframework.statemachine
spring-statemachine-redis
1.2.9.RELEASE
redis配置
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
跟上面map存储一样,在配置文件内配置redis信息
PersistConfig.java
@Autowired
private RedisConnectionFactory redisConnectionFactory;
...
/**
* 注入RedisStateMachinePersister对象
*
* @return
*/
@Bean(name = "orderRedisPersister")
public RedisStateMachinePersister redisPersister() {
return new RedisStateMachinePersister<>(redisPersist());
}
/**
* 通过redisConnectionFactory创建StateMachinePersist
*
* @return
*/
public StateMachinePersist redisPersist() {
RedisStateMachineContextRepository repository =
new RedisStateMachineContextRepository<>(redisConnectionFactory);
return new RepositoryStateMachinePersist<>(repository);
}
调用
@RequestMapping("/testRedisPersister")
public void testRedisPersister(String id) throws Exception {
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
stateMachine.start();
Order order = new Order();
order.setId(id);
//发送PAY事件
Message message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).build();
stateMachine.sendEvent(message);
//持久化stateMachine
orderRedisPersister.persist(stateMachine, order.getId());
}
@RequestMapping("/testRedisPersisterRestore")
public void testRestore(String id) throws Exception {
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
orderRedisPersister.restore(stateMachine, id);
System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
}
在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程......,所以这种可以任意调节状态的才是我们需要的状态机。
简而言之就是直接拿到状态机的一个中间过成,并可以继续执行。
回到persisit的实现类,既然目的是为了拿到一个中间段的状态机,那么就没有必要存当前状态了
@Component
public class OrderStateMachinePersist implements StateMachinePersist {
@Override
public void write(StateMachineContext context, Order contextObj) throws Exception {
//这里不做任何持久化工作
}
@Override
public StateMachineContext read(Order contextObj) throws Exception {
StateMachineContext result = new DefaultStateMachineContext(OrderStates.valueOf(contextObj.getState()),
null, null, null, null, "orderMachine");
return result;
}
}
跟之前的步骤一样把persist注入到config文件中
@Configuration
public class PersistConfig {
@Autowired
private OrderStateMachinePersist orderStateMachinePersist;
@Bean(name="orderPersister")
public StateMachinePersister orderPersister() {
return new DefaultStateMachinePersister(orderStateMachinePersist);
}
}
在执行的地方用通过persisiter拿到操作类
@RestController
@RequestMapping("/statemachine")
public class StateMachineController {
@Resource(name="orderPersister")
private StateMachinePersister persister;
@RequestMapping("/testOrderRestore")
public void testOrderRestore(String id) throws Exception {
StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
//订单
Order order = new Order();
order.setId(id);
order.setState(OrderStates.WAITING_FOR_RECEIVE.toString());
//恢复
persister.restore(stateMachine, order);
System.out.println("恢复后的状态:" + stateMachine.getState().getId());
stateMachine.sendEvent(OrderEvents.RECEIVE);
//查看恢复后状态机的状态
System.out.println("执行下一步后的状态:" + stateMachine.getState().getId());
}
}
构建订单状态机
恢复后的状态:WAITING_FOR_RECEIVE
传递的参数:null
传递的参数:null
执行下一步后的状态:DONE