1.什么是statemachine?

Spring Statemachine 是应用程序开发人员在 Spring 应用程序中使用状态机概念的框架,从设计层面分析:状态机目的是解决复杂的状态管理流程,保证程序单一原则和开闭原则;业务角度分析:状态机应有初始化状态、加载所有已有的状态、事件、转移、动作、触发下一个状态为驱动,并解决了业务逻辑与状态管理之间的强耦合。

Spring Statemachine 提供以下功能:

  • 易于使用的平面(一级)状态机,适用于简单的用例。
  • 分层状态机结构,以简化复杂的状态配置。
  • 状态机区域提供更复杂的状态配置。
  • 触发器、转换、守卫和动作的使用。
  • 类型安全的配置适配器。
  • 状态机事件监听器。
  • Spring IoC 集成将 bean 与状态机相关联。

Spring Statemachine 原理

Spring状态机建立在有限状态机(FSM)的概念之上,提供了一种简洁且灵活的方式来定义、管理和执行状态机。它将状态定义为Java对象,并通过配置来定义状态之间的转换规则。状态转换通常由外部事件触发,我们可以根据业务逻辑定义不同的事件类型,并与状态转换关联。Spring状态机还提供了状态监听器,用于在状态变化时执行特定的逻辑。同时,状态机的状态可以持久化到数据库或其他存储介质中,以便在系统重启或故障恢复时保持状态的一致性。 Spring状态机核心主要包括以下三个关键元素:

  1. 状态(State):定义了系统可能处于的各个状态,如订单状态中的待支付、已支付等。
  2. 转换(Transition):描述了在何种条件下,当接收到特定事件时,系统可以从一个状态转移到另一个状态。例如,接收到“支付成功”事件时,订单状态从“待支付”转变为“已支付”。
  3. 事件(Event):触发状态转换的动作或者消息,它是引起状态机从当前状态迁移到新状态的原因。

接下来,我们将上述状态模式中关于订单状态的示例转换为状态机实现。  

2.代码工程

实验目标:订单状态的示例转换为状态机实现。

pom.xml

<?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>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Statemachine</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>
</project>

定义状态机的状态以及事件类型

定义状态(States): 状态是状态机的核心组成单元,代表了系统或对象在某一时刻可能存在的条件或模式。在状态机中,每一个状态都是系统可能处于的一种明确的条件或阶段。例如,在一个简单的咖啡机状态机中,可能有的状态包括“待机”、“磨豆”、“冲泡”和“完成”。每个状态都是独一无二的,且在任何给定时间,系统只能处于其中一个状态。 定义转换(Transitions): 转换则是指状态之间的转变过程,它是状态机模型动态性的体现。当一个外部事件(如用户按下按钮、接收到信号、满足特定条件等)触发时,状态机会从当前状态转移到另一个状态。在定义转换时,需要指出触发转换的事件(Event)以及事件发生时系统的响应,即从哪个状态(Source State)转到哪个状态(Target State)。

package com.et.statemachine.state;

/**
 * @description:order status
 */
public enum OrderStatusChangeEventEnum {

    PAYED,

    DELIVERY,

    RECEIVED;
}
package com.et.statemachine.state;

/**
 * @description: order status
 */
public enum OrderStatusEnum {

    WAIT_PAYMENT,

    WAIT_DELIVER,

    WAIT_RECEIVE,

    FINISH;
}

定义状态机以及状态流转规则

状态机配置类是在使用Spring State Machine或其他状态机框架时的一个重要步骤,这个类主要用于定义状态机的核心结构,包括状态(states)、事件(events)、状态之间的转换规则(transitions),以及可能的状态迁移动作和决策逻辑。 在Spring State Machine中,创建状态机配置类通常是通过继承StateMachineConfigurerAdapter类来实现的。这个适配器类提供了几个模板方法,允许开发者重写它们来配置状态机的各种组成部分:

  1. 配置状态configureStates(StateMachineStateConfigurer)): 在这个方法中,开发者定义状态机中所有的状态,包括初始状态(initial state)和结束状态(final/terminal states)。例如,定义状态A、B、C,并指定状态A作为初始状态。
  2. 配置转换configureTransitions(StateMachineTransitionConfigurer)): 在这里,开发者描述状态之间的转换规则,也就是当某个事件(event)发生时,状态机应如何从一个状态转移到另一个状态。例如,当事件X发生时,状态机从状态A转移到状态B。
  3. 配置初始状态configureInitialState(ConfigurableStateMachineInitializer)): 如果需要显式指定状态机启动时的初始状态,可以在该方法中设置。
package com.et.statemachine.config;

import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import java.util.EnumSet;

/**
 * @description: order statemachine
 */
@Configuration
@EnableStateMachine
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {

    /**
     * configure state
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {
        states.withStates()
                .initial(OrderStatusEnum.WAIT_PAYMENT)
                .end(OrderStatusEnum.FINISH)
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }

    /**
     * configure state transient  with event
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> transitions) throws Exception {
        transitions.withExternal().source(OrderStatusEnum.WAIT_PAYMENT).target(OrderStatusEnum.WAIT_DELIVER)
                .event(OrderStatusChangeEventEnum.PAYED)
                .and()
                .withExternal().source(OrderStatusEnum.WAIT_DELIVER).target(OrderStatusEnum.WAIT_RECEIVE)
                .event(OrderStatusChangeEventEnum.DELIVERY)
                .and()
                .withExternal().source(OrderStatusEnum.WAIT_RECEIVE).target(OrderStatusEnum.FINISH)
                .event(OrderStatusChangeEventEnum.RECEIVED);
    }
}

定义状态机监听器

状态机监听器(State Machine Listener)是一种组件,它可以监听并响应状态机在运行过程中的各种事件,例如状态变迁、进入或退出状态、转换被拒绝等。 在Spring Statemachine中,监听器可以通过实现StateMachineListener接口来定义。该接口提供了一系列回调方法,如transitionTriggeredstateEnteredstateExited等,当状态机触发转换、进入新状态或离开旧状态时,这些方法会被调用。同时,我们也可以通过注解实现监听器。注解方式可以在类的方法上直接声明该方法应该在何种状态下被调用,简化监听器的编写和配置。例如@OnTransition@OnTransitionEnd@OnTransitionStart

package com.et.statemachine.listener;

import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @description: state listener
 */
@Component
@WithStateMachine
@Transactional
public class OrderStatusListener {
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);
        System.out.println("pay,feedback by statemachine:" + message.getHeaders().toString());
        return true;
    }

    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);
        System.out.println("deliver,feedback by statemachine:" + message.getHeaders().toString());
        return true;
    }

    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.FINISH);
        System.out.println("receive,feedback by statemachine:" + message.getHeaders().toString());
        return true;
    }

}

service

package com.et.statemachine.service;

import com.et.statemachine.state.Order;

import java.util.Map;

/**
 * @author liuhaihua
 * @version 1.0
 * @ClassName OrderService
 * @Description todo
 * @date 2024年05月27日 15:15
 */

public interface OrderService {

    Order create();

    Order pay(long id);

    Order deliver(long id);

    Order receive(long id);

    Map<Long, Order> getOrders();
}
package com.et.statemachine.service;

import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @description: order service
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;

    private long id = 1L;

    private Map<Long, Order> orders = new ConcurrentHashMap<>();

    @Override
    public Order create() {
        Order order = new Order();
        order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);
        order.setOrderId(id++);
        orders.put(order.getOrderId(), order);
        System.out.println("order create success:" + order.toString());
        return order;
    }

    @Override
    public Order pay(long id) {
        Order order = orders.get(id);
        System.out.println("try to pay,order no:" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).
                setHeader("order", order).build();
        if (!sendEvent(message)) {
            System.out.println(" pay fail, error,order no:" + id);
        }
        return orders.get(id);
    }

    @Override
    public Order deliver(long id) {
        Order order = orders.get(id);
        System.out.println(" try to deliver,order no:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY)
                .setHeader("order", order).build())) {
            System.out.println(" deliver fail,error,order no:" + id);
        }
        return orders.get(id);
    }

    @Override
    public Order receive(long id) {
        Order order = orders.get(id);
        System.out.println(" try to receiver,order no:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED)
                .setHeader("order", order).build())) {
            System.out.println(" deliver fail,error,order no:" + id);
        }
        return orders.get(id);
    }


    @Override
    public Map<Long, Order> getOrders() {
        return orders;
    }

    /**
     * send transient  event
     * @param message
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEventEnum> message) {
        boolean result = false;
        try {
            orderStateMachine.start();
            result = orderStateMachine.sendEvent(message);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(message)) {
                Order order = (Order) message.getHeaders().get("order");
                if (Objects.nonNull(order) && Objects.equals(order.getOrderStatus(), OrderStatusEnum.FINISH)) {
                    orderStateMachine.stop();
                }
            }
        }
        return result;
    }
}

controller

package com.et.statemachine.controller;

import com.et.statemachine.service.OrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
public class HelloWorldController {
    @RequestMapping("/hello")
    public Map<String, Object> showHelloWorld(){
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "HelloWorld");
        return map;
    }
    @Resource
    private OrderService orderService;

    @RequestMapping("/testOrderStatusChange")
    public String testOrderStatusChange(){
        orderService.create();
        orderService.create();
        orderService.pay(1L);
        orderService.deliver(1L);
        orderService.receive(1L);
        orderService.pay(2L);
        orderService.deliver(2L);
        orderService.receive(2L);
        System.out.println("all orders:" + orderService.getOrders());
        return "success";
    }

}

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

3.测试

启动Spring Boot应用

测试状态机

访问http://127.0.0.1:8088/testOrderStatusChange,查看控制台输出

order create success:Order(orderId=1, orderStatus=WAIT_PAYMENT)
order create success:Order(orderId=2, orderStatus=WAIT_PAYMENT)
try to pay,order no:1
2024-05-27 22:58:14.208 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.209 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_DELIVER), id=fc2c0720-e6ba-9bf8-d359-72a6c61b4186, timestamp=1716821894196}
 try to deliver,order no:1
deliver,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_RECEIVE), id=e743d376-22e1-bfc3-1c62-7131ff1bf7c1, timestamp=1716821894227}
 try to receiver,order no:1
receive,feedback by statemachine:{order=Order(orderId=1, orderStatus=FINISH), id=652167b8-e74f-bde2-62f7-94bdbb5bad7e, timestamp=1716821894229}
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
try to pay,order no:2
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_DELIVER), id=d331fa76-8a28-aaa7-6257-a9404c2084d6, timestamp=1716821894230}
 try to deliver,order no:2
deliver,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_RECEIVE), id=4e930443-6b04-fd86-6740-5631db2aea1d, timestamp=1716821894232}
 try to receiver,order no:2
receive,feedback by statemachine:{order=Order(orderId=2, orderStatus=FINISH), id=6473cc9e-5cd9-0de5-12c8-7d51dd3f9da6, timestamp=1716821894233}
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
all orders:{1=Order(orderId=1, orderStatus=FINISH), 2=Order(orderId=2, orderStatus=FINISH)}

4.引用

   

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐