0

TopDuang的个人空间

 2 years ago
source link: https://my.oschina.net/u/2378709/blog/5438255
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

有限状态机(FSM)java实现

1. 有限状态机介绍

有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。有限状态机成立的必要条件有:

  1. 对象有一组互斥的状态(或对象的生命周期),且这组状态可以涵盖对象的创建到消亡的整个过程。
  2. 当向对象传入一个信号(或事件)时,对象的状态能从当前状态转换成另一种状态,或者保持状态不变。
  3. 状态是有限的。

如图例所示,红绿灯在同一时间只能亮一个颜色,控制程序可以定义3种不同的事件,每个事件定义好红绿灯的起始颜色和目标颜色,我们不需要直接去操作红绿灯开关,只需要按照一定的顺序发送事件过去,我们就可以精确控制红绿灯的工作,红绿灯的工作控制其实就是一个标准的FSM。

由上所述,FSM一般需要以下4个部分组成:

  1. 对象状态枚举。
  2. 对象事件枚举,并指定事件的起始状态和目标状态。
  3. 事件逻辑体,用于处理状态变更引起的业务逻辑。
  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

到这里FSM的封装就完成了,其中有一些设计可以根据自身技术选择进行调整,如工厂类的单例实现可以使用spring的bean注入方式,事件和状态的绑定关系可以外挂出来作为配置项等。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK