有限状态机(FSM)java实现
1. 有限状态机介绍
有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。有限状态机成立的必要条件有:
- 对象有一组互斥的状态(或对象的生命周期),且这组状态可以涵盖对象的创建到消亡的整个过程。
- 当向对象传入一个信号(或事件)时,对象的状态能从当前状态转换成另一种状态,或者保持状态不变。
- 状态是有限的。
如图例所示,红绿灯在同一时间只能亮一个颜色,控制程序可以定义3种不同的事件,每个事件定义好红绿灯的起始颜色和目标颜色,我们不需要直接去操作红绿灯开关,只需要按照一定的顺序发送事件过去,我们就可以精确控制红绿灯的工作,红绿灯的工作控制其实就是一个标准的FSM。
由上所述,FSM一般需要以下4个部分组成:
- 对象状态枚举。
- 对象事件枚举,并指定事件的起始状态和目标状态。
- 事件逻辑体,用于处理状态变更引起的业务逻辑。
- 事件注册工厂类,FSM的唯一入口。
2. 从需求开始分析FSM
当我们拿到一个类似需求时应该怎么入手呢?下面我们以一个简单的商城订单的FSM实现为例,从需求分析到代码实现为大家讲解:
首先我们应该从需求中提炼出一个订单具体需要经过哪些状态(这里我只列举了一个简单订单的正向状态)。
订单状态:待支付、待发货、待收货、已取消、已完成
列举出所有状态之后,在作图工具中把状态全部画出来,每个状态进行分析是否能转换为其他状态,如分析待支付状态,用户可以从待支付状态进行付款事件,待支付状态将会转换为待发货,用户也可以从待支付状态取消支付,待支付将会转换为订单取消状态。按照这个思路使用单向箭头将所有事件列举出来,并给每个事件起名字。
订单事件:下单事件、支付事件、发货事件、支付取消事件、收货事件
最后形成如下图所示的状态流转图
3. FSM的java实现
按照上面所说,我们将FSM的4个部分声明出来:
3.1. 对象状态枚举类
使用枚举的方式穷举出订单所有可能的状态。
public enum OrderStatusEnum {
Unpaid("Unpaid", "待支付"),
UnShipping("UnShipping", "待发货"),
UnReceiving("UnReceiving", "待收货"),
Canceled("Canceled", "已取消"),
Finished("Finished", "已完成");
private final String code;
private final String desc;
OrderStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
3.2. 对象事件枚举类
为了省事,我在这里把事件和状态做了关联,也可以单独拿出来做一个配置封装。
public enum OrderEventEnum {
CreateOrder("CreateOrder", "下单事件", null, OrderStatusEnum.Unpaid),
Payment("Payment", "支付事件", OrderStatusEnum.Unpaid, OrderStatusEnum.UnShipping),
Shipping("Shipping", "发货事件", OrderStatusEnum.UnShipping, OrderStatusEnum.UnReceiving),
Receiving("Receiving", "收货事件", OrderStatusEnum.UnReceiving, OrderStatusEnum.Finished),
CancelPayment("CancelPayment", "支付取消事件", OrderStatusEnum.Unpaid, OrderStatusEnum.Canceled);
private final String code;
private final String desc;
private final OrderStatusEnum sourceOrderStatus;
private final OrderStatusEnum targetOrderStatus;
OrderEventEnum(String code, String desc, OrderStatusEnum sourceOrderStatus, OrderStatusEnum targetOrderStatus) {
this.code = code;
this.desc = desc;
this.sourceOrderStatus = sourceOrderStatus;
this.targetOrderStatus = targetOrderStatus;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public OrderStatusEnum getSourceOrderStatus() {
return sourceOrderStatus;
}
public OrderStatusEnum getTargetOrderStatus() {
return targetOrderStatus;
}
}
3.3. 事件逻辑体
首先声明一个逻辑体基类,基类里面定义了事件过程数据缓存对象、事件条件校验方法、事件后处理方法、事件业务逻辑处理虚方法(需要由业务自己实现)。
@Slf4j
public abstract class BaseOrderFsmProcessor {
private static final Map<Long, Object> FSM_DATA_MAP = new ConcurrentHashMap<>();
/**
* 执行业务逻辑
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
log.info("OrderFSM_开始FSM事件:orderId={}", orderId);
if (!checkRule(orderId, event)) {
log.warn("OrderFSM_不满足条件,拒绝执行FSM事件:orderId={}", orderId);
return true;
}
boolean b = process(orderId, event);
log.info("OrderFSM_结束FSM事件:orderId={}", orderId);
postHandler(orderId, event);
return b;
}
/**
* 业务逻辑实现类
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
* @throws Exception ex
*/
public abstract boolean process(Long orderId, OrderEventEnum event) throws Exception;
/**
* 校验是否满足条件执行当亲process
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
return true;
}
/**
* 后置处理逻辑
*
* @param orderId 订单ID
* @param event 事件类型
*/
public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
}
/**
* 根据流程ID获取事件过程数据
*
* @param orderId 订单ID
* @return 事件过程数据
*/
public static Object getFsmData(Long orderId) {
return FSM_DATA_MAP.remove(orderId);
}
/**
* 根据流程ID设置事件过程数据
*
* @param orderId 订单ID
* @param obj 事件过程数据
*/
public static void setFsmData(Long orderId, Object obj) {
FSM_DATA_MAP.put(orderId, obj);
}
}
再跟进基类派生出不同事件对应的处理方法,一个事件要声明一个对应的方法:
@Slf4j
public class CreateOrderProcessor extends BaseOrderFsmProcessor {
@Override
public boolean process(Long orderId, OrderEventEnum event) throws Exception {
log.info("业务逻辑执行中:className={},event={}", getClass().getSimpleName(), event.name());
//TODO 模拟业务逻辑
TimeUnit.MILLISECONDS.sleep(1000);
setFsmData(orderId, Thread.currentThread().getName());
log.info("业务逻辑执行完成");
return true;
}
@Override
public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
log.info("执行条件检查通过");
return true;
}
@Override
public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
log.info("执行后置处理逻辑");
// TODO 将orderId的状态改为 event.getTargetOrderStatus()
}
}
3.4. 事件注册工厂类
工厂类里封装了单例FSM执行的唯一入口和查询设置流程中间数据的入口。
@Slf4j
public class OrderFsmManager {
private final Map<OrderEventEnum, BaseOrderFsmProcessor> orderProcessorMap = new HashMap<>();
private volatile static OrderFsmManager orderFsmManager;
private OrderFsmManager() {
orderProcessorMap.put(OrderEventEnum.CreateOrder, new CreateOrderProcessor());
orderProcessorMap.put(OrderEventEnum.Payment, new PaymentFsmProcessor());
}
/**
* 获取fsm实例
*/
public static OrderFsmManager getInstance() {
if (orderFsmManager == null) {
synchronized (OrderFsmManager.class) {
if (orderFsmManager == null) {
orderFsmManager = new OrderFsmManager();
}
}
}
return orderFsmManager;
}
/**
* 开始执行fsm事件
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
if (!orderProcessorMap.containsKey(event)) {
throw new Exception(String.format("MediaProcessFSM没有匹配到事件:orderId=%s,currentOrderEvent=%s"
, orderId, event));
}
return orderProcessorMap.get(event).fireProcess(orderId, event);
}
/**
* 根据流程ID获取事件过程数据
*
* @param orderId 订单ID
* @return 事件过程数据
*/
public Object getFsmData(Long orderId) {
return BaseOrderFsmProcessor.getFsmData(orderId);
}
/**
* 根据流程ID设置事件过程数据
*
* @param orderId 订单ID
* @param obj 事件过程数据
*/
public void setFsmData(Long orderId, Object obj) {
BaseOrderFsmProcessor.setFsmData(orderId, obj);
}
}
3.5. 简单做个测试
public static void main(String[] args) throws Exception {
OrderFsmManager orderFsmManager = OrderFsmManager.getInstance();
// boolean b1 = orderFsmManager.fireProcess(1L, OrderEventEnum.CreateOrder);
// boolean b2 = orderFsmManager.fireProcess(2L, OrderEventEnum.Payment);
// System.out.println(String.format("orderId=%s,data=%s",1, orderFsmManager.getFsmData(1L)));
// System.out.println(String.format("orderId=%s,data=%s",2, orderFsmManager.getFsmData(2L)));
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
String threadName = "thread-" + finalI;
Thread.currentThread().setName(threadName);
try {
if (finalI%2==0){
boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.CreateOrder);
}else {
boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.Payment);
}
System.out.println(String.format("threadName=%s,data=%s",threadName, orderFsmManager.getFsmData((long) finalI)));
} catch (Exception exception) {
exception.printStackTrace();
}
}).start();
}
}
11:17:39.213 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=2
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.213 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=1
11:17:39.213 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=0
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行条件检查通过
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行中:className=PaymentFsmProcessor,event=Payment
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行完成
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=1
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行后置处理逻辑
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=2
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-1,data=thread-1
threadName=thread-2,data=thread-2
11:17:40.246 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=0
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-0,data=thread-0
4. 结语
到这里FSM的封装就完成了,其中有一些设计可以根据自身技术选择进行调整,如工厂类的单例实现可以使用spring的bean注入方式,事件和状态的绑定关系可以外挂出来作为配置项等。