状态机简介
先从状态机的定义入手,StateMachine,其中:
- StateMachine:状态机模型
- state:S-状态,一般定义为一个枚举类,如创建、待风控审核、待支付等状态
- event:E-事件,同样定义成一个枚举类,如订单创建、订单审核、支付等,代表一个动作。
- 一个状态机的定义就由这两个主要的元素组成,状态及对对应的事件(动作)。
状态机的相关概念
- Transition: 节点,是组成状态机引擎的核心
- source:节点的当前状态
- target:节点的目标状态
- event:触发节点从当前状态到目标状态的动作
- guard:起校验功能,一般用于校验是否可以执行后续action
- action:用于实现当前节点对应的业务逻辑处理
状态机的持久化
每次用到的时候新创建一个状态机,太奢侈了,官方文档里面也提到过这点。而且创建出来的实例,其状态也跟当前订单的不符;spring statemachine暂时不支持每次创建时指定当前状态,所以对状态机引擎实例的持久化,就成了必须要考虑的问题。
spring statemachine 本身支持了内存、redis及db的持久化,内存持久化就不说了,看源码实现就是放在了hashmap里,平时也没谁项目中可以这么奢侈,啥啥都放在内存中,而且一旦重启…(嘿嘿嘿)。下面详细说下利用redis进行的持久化操作。
spring statemachine持久化时,采用了三层结构设计,persister —>persist —>repository。
- 其中persister中封装了write和restore两个方法,分别用于持久化写及反序列化读出。
- persist只是一层皮,主要还是调用repository中的实际实现,这里可以使用
- repository中做了两件事儿
- 序列化/反序列化数据,将引擎实例与二进制数组互相转换
- 读、写redis
Persister
接口
代码语言:javascript复制public interface StateMachinePersister<S, E, T> {
void persist(StateMachine<S, E> var1, T var2) throws Exception;
StateMachine<S, E> restore(StateMachine<S, E> var1, T var2) throws Exception;
}
接口点进去其抽象类的实现,可以看到使用的是StateMachinePersist的read与write方法
代码语言:javascript复制public abstract class AbstractStateMachinePersister<S, E, T> implements StateMachinePersister<S, E, T> {
private final StateMachinePersist<S, E, T> stateMachinePersist;
public final void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception {
this.stateMachinePersist.write(this.buildStateMachineContext(stateMachine), contextObj);
}
public final StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception {
final StateMachineContext<S, E> context = this.stateMachinePersist.read(contextObj);
stateMachine.stop();
stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
public void apply(StateMachineAccess<S, E> function) {
function.resetStateMachine(context);
}
});
stateMachine.start();
return stateMachine;
}
}
Persist
代码语言:javascript复制public interface StateMachinePersist<S, E, T> {
void write(StateMachineContext<S, E> var1, T var2) throws Exception;
StateMachineContext<S, E> read(T var1) throws Exception;
}
自定义实现
代码语言:javascript复制@Component
public class OrderStateMachinePersist implements StateMachinePersist<OrderStatusEnum, OrderChangeEventEnum, PersisterDO> {
@Autowired
private TbUserOrderMapper userOrderMapper;
//将订单状态写入数据库
@Override
public void write(StateMachineContext<OrderStatusEnum, OrderChangeEventEnum> stateMachineContext, PersisterDO persisterDO) throws Exception {
String orderNumber = persisterDO.getOrderNumber();
if(orderNumber == null){
throw new RuntimeException("orderNumber 为空");
}
QueryWrapper<TbUserOrder> wrapper = new QueryWrapper<>();
wrapper.eq("order_number",orderNumber);
TbUserOrder order = userOrderMapper.selectOne(wrapper);
if(order != null){
order.setOrderState(stateMachineContext.getState().name());
order.setUpdateTime(new Date());
userOrderMapper.updateById(order);
}
}
@Override
public StateMachineContext<OrderStatusEnum, OrderChangeEventEnum> read(PersisterDO persisterDO) throws Exception {
//将数据从数据库中读出,然后将订单的状态设置给参数orderStatusEnum
OrderStatusEnum orderStatusEnum;
QueryWrapper<TbUserOrder> wrapper = new QueryWrapper<>();
wrapper.eq("order_number",persisterDO.getOrderNumber());
TbUserOrder order = userOrderMapper.selectOne(wrapper);
if(order != null){
orderStatusEnum = OrderStatusEnum.valueOf(order.getOrderState());
}else{
orderStatusEnum = OrderStatusEnum.CREATE;
}
return new DefaultStateMachineContext<>(orderStatusEnum, null, null, null, null, persisterDO.getMachineId());
}
}
状态变迁
状态枚举就是当前订单所处的状态,事件会导致订单的状态发生改变(但是也不一定,有些是内部事件,并不会导致状态发生变化)
状态枚举
代码语言:javascript复制public enum OrderStatusEnum {
CREATE("新建"),
WAIT_PAYMENT("待付款"),
WAIT_SEND("待发货"),
WAIT_RECEIVE("待收货"),
COMPLETED("已完成"),
CANCEL("取消");
private final String name;
}
事件枚举
代码语言:javascript复制public enum OrderChangeEventEnum {
CREATE_ORDER("创建订单"),
PAY_ORDER("支付订单"),
PAY_CANCEL("取消支付"),
SEND_GOODS("发货"),
RECEIVE_GOODS("收货"),
;
private final String name;
}
状态机的配置
状态机的配置有两种方式
- 创建config类,实现StateMachineConfigurer(或者根据SE的不同,直接继承其子类StateMachineConfigurerAdapter、EnumStateMachineConfigurerAdapter),然后分别重写其不同的configure方法,用于指定对应配置。
- 依然实现StateMachineConfigurer(或继承其子类),不过通过StateMachineBuilder.builder()来指定对应配置。
自定义Builder
自定义一个Builder接口,用来规范不同业务状态机的配置
代码语言:javascript复制public interface IStateMachineBuilder<S, E> {
String getName();
StateMachine<S, E> build(BeanFactory beanFactory) throws Exception;
//订单状态机构造器
String ORDER_BUILDER_NAME = "orderStateMachineBuilder";
}
设置对应业务Builder
设置对应业务的Builder,外部调用的使用直接使用build()方法就行
这个里面的Guard就可以相当于一个条件,如果不满足Guard(即返回false)那么就不会执行接下来的action
Action就相当于一个执行的过程,其中errorHandlerAction就是对异常的处理,其中比较重要的是
- withExternal 是当source和target不同时的写法,比如付款成功后状态发生的变化。
- withInternal 当source和target相同时的串联写法,比如付款失败后都是待付款状态。
- withExternal的source和target用于执行前后状态、event为触发的事件、guard判断是否执行action。同时满足source、target、event、guard的条件后执行最后的action。
- withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice guard来跳转
- withChoice根据guard的判断结果执行first/then的逻辑。
- withChoice不需要发送事件来进行触发。
public class OrderStateMachineBuilder implements IStateMachineBuilder<OrderStatusEnum, OrderChangeEventEnum> {
@Autowired
private Guard<OrderStatusEnum, OrderChangeEventEnum> orderCreateGuard;
@Autowired
private Action<OrderStatusEnum, OrderChangeEventEnum> errorHandlerAction;
@Autowired
private Action<OrderStatusEnum, OrderChangeEventEnum> orderCreateAction;
@Override
public StateMachine<OrderStatusEnum, OrderChangeEventEnum> build(BeanFactory beanFactory) throws Exception {
StateMachineBuilder.Builder<OrderStatusEnum, OrderChangeEventEnum> builder = StateMachineBuilder.builder();
//设置id
builder.configureConfiguration()
.withConfiguration()
.autoStartup(true)
.beanFactory(beanFactory)
.machineId(ORDER_BUILDER_NAME "Id");
//初始化状态机,并指定状态集合
builder.configureStates()
.withStates()
//初始状态
.initial(OrderStatusEnum.CREATE)
.end(OrderStatusEnum.COMPLETED)
.states(EnumSet.allOf(OrderStatusEnum.class));
//定义状态机节点,即迁移动作
builder.configureTransitions()
.withExternal()
.source(OrderStatusEnum.CREATE)
.target(OrderStatusEnum.WAIT_PAYMENT)
.event(OrderChangeEventEnum.CREATE_ORDER)
.guard(orderCreateGuard)
.action(orderCreateAction, errorHandlerAction)
...这可以使用and()继续写
;
return builder.build();
}
@Override
public String getName() {
return ORDER_BUILDER_NAME;
}
}
状态机工厂
在实际项目中一般都会有多个状态机并发执行,比如订单,同一时刻会有不止一个订单在运行,而每个订单都有自己的订单状态机流程。所以如果使用配置类的话就只有一个状态机,所以需要使用Builder,同时因为可以会有多种类型的状态机,所以定义了一个接口,后续类型的状态机只要实现这个状态机接口就可以开发
代码语言:javascript复制@Component
public class StateMachineBuildFactory<S, E> implements ApplicationContextAware {
@Autowired
private BeanFactory beanFactory;
/**
* 用来存储builder-name及builder的map
*/
public static final Map<String, IStateMachineBuilder> builderMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
String[] beanNamesForType = applicationContext.getBeanNamesForType(IStateMachineBuilder.class);
for (String beanName : beanNamesForType) {
IStateMachineBuilder bean = (IStateMachineBuilder) applicationContext.getBean(beanName);
builderMap.put(bean.getName(), bean);
}
}
public StateMachine<S, E> createStateMachine(String machineType) throws Exception{
if (StringUtils.isBlank(org.apache.commons.lang3.StringUtils.trim(machineType))) {
throw new RuntimeException("无效的状态机类型");
}
IStateMachineBuilder builder = builderMap.get(machineType);
StateMachine<S, E> stateMachine;
try {
stateMachine = builder.build(beanFactory);
} catch (Exception e) {
throw new RuntimeException("创建状态机异常");
}
return stateMachine;
}
}
状态机的使用
外部调用状态机需要以下三步
- 通过创建/读取的方式获取当前订单对应的状态机引擎实例
- 构造message
- 发送message
需要注意的是当发送完message之后,spring statemachine才会通过监听器来监听走那个action,只有在message完成之后才会更新为target状态
所以为了更加方便管理使用,所以做了以下代码封装
接口Manager
这规定了这个Manager必须实现的方法,就是来发送message
代码语言:javascript复制public interface IStateMachineEventManager<E>{
/**
* 状态发生改变变更事件
* @param statusModelDO
* @param eventEnum
* @param <R>
* @return
*/
<R> R sendStatusChangeEvent(StatusModelDO statusModelDO, E eventEnum);
}
抽象类
这个里面主要实现初始化状态机,构造message与发送message
代码语言:javascript复制public abstract class AbstractStateMachineEventManager<S, E> implements IStateMachineEventManager<E> {
@Autowired
private StateMachineBuildFactory<S, E> stateMachineBuildFactory;
//用于状态机上下文持久化
public abstract void stateMachinePersist(StateMachine<S, E> stateMachine, PersisterDO persisterDO) throws Exception;
//用于状态机上下文初始化
public abstract StateMachine<S, E> stateMachineRestore(StateMachine<S, E> stateMachine, PersisterDO persisterDO) throws Exception;
@Override
@SuppressWarnings("unchecked")
public <R> R sendStatusChangeEvent(StatusModelDO statusModelDO, E eventEnum) {
StateMachine<S, E> stateMachine = initializeMachine(statusModelDO);
Boolean result = statusChangeExecute(stateMachine, statusModelDO, eventEnum);
if (!result) {
throw new RuntimeException("状态机状态执行失败");
}
RuntimeException exception = (RuntimeException) stateMachine.getExtendedState().getVariables().get(RuntimeException.class);
if (exception != null) {
}
try {
PersisterDO persisterDO = new PersisterDO();
persisterDO.setOrderNumber(statusModelDO.getOrderNumber());
stateMachinePersist(stateMachine, persisterDO);
} catch (Exception e) {
throw new RuntimeException("状态机持久化失败");
}
return (R) stateMachine.getExtendedState().getVariables().get(StateMachineConstants.RETURN_PARAM);
}
private StateMachine<S, E> initializeMachine(StatusModelDO statusModelDO) {
StateMachine<S, E> stateMachine;
try {
StateMachine<S, E> srcStateMachine = stateMachineBuildFactory.createStateMachine(getStateMachineType());
PersisterDO persisterDO = new PersisterDO();
persisterDO.setOrderNumber(statusModelDO.getOrderNumber());
persisterDO.setCurrentState(statusModelDO.getCurrentState());
stateMachine = stateMachineRestore(srcStateMachine, persisterDO);
} catch (Exception e) {
throw new RuntimeException("初始化状态机失败");
}
if (stateMachine == null) {
throw new RuntimeException("没有找到可用的状态机");
}
return stateMachine;
}
private boolean statusChangeExecute(StateMachine<S, E> stateMachine, StatusModelDO statusModelDO, E eventEnum) {
Message<E> eventMsg = MessageBuilder.withPayload(eventEnum)
.setHeader(StateMachineConstants.STATE_MODEL_DTO, statusModelDO).build();
if (!acceptEvent(stateMachine, eventMsg)) {
throw new RuntimeException("找不到对应状态机事件触发定义");
}
return stateMachine.sendEvent(eventMsg);
}
private static <S, E> boolean acceptEvent(StateMachine<S, E> stateMachine, Message<E> eventMsg) {
//获取当前状态
State<S, E> currentState = stateMachine.getState();
for (Transition<S, E> transition : stateMachine.getTransitions()) {
State<S, E> source = transition.getSource();
Trigger<S, E> trigger = transition.getTrigger();
if (currentState != null && trigger != null &&
StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds()) &&
trigger.evaluate(new DefaultTriggerContext<>(eventMsg.getPayload()))) {
return true;
}
}
return false;
}
public abstract String getStateMachineType();
}
实现类
这里主要实现的是上下文的初始化与持久化,这个就可以直接调用状态机的持久化里面的read与write就行
代码语言:javascript复制@Component
public class OrderStateMachineEventManager extends AbstractStateMachineEventManager<OrderStatusEnum, OrderChangeEventEnum> {
@Resource(name = "orderStateMachinePersister")
private StateMachinePersister<OrderStatusEnum, OrderChangeEventEnum, PersisterDO> stateMachinePersister;
@Override
public void stateMachinePersist(StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachine,
PersisterDO persisterDO) throws Exception {
stateMachinePersister.persist(stateMachine, persisterDO);
}
@Override
public StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachineRestore(
StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachine,
PersisterDO persisterDO) throws Exception {
return stateMachinePersister.restore(stateMachine, persisterDO);
}
@Override
public String getStateMachineType() {
return IStateMachineBuilder.ORDER_BUILDER_NAME;
}
}
参考文档
Github代码
状态机引擎选型
官网
reference