写点什么

玩转 Spring 状态机

  • 2024-06-24
    北京
  • 本文字数:6100 字

    阅读完需:约 20 分钟

说起 Spring 状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring 状态机就是状态模式的一种实现,在介绍 Spring 状态机之前,让我们来看看设计模式中的状态模式。

1. 状态模式

状态模式的定义如下:


状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为。在状态模式中,一个对象的行为取决于其当前状态,而且可以随时改变这个状态。状态模式将对象的状态封装在不同的状态类中,从而使代码更加清晰和易于维护。当一个对象的状态改变时,状态模式会自动更新该对象的行为,而不需要在代码中手动进行判断和处理。


通常业务系统中会存在一些拥有状态的对象,而且这些状态之间可以进行转换,并且在不同的状态下会表现出不同的行为或者不同的功能,比如交通灯控制系统中会存在红灯、绿灯和黄灯,再比如订单系统中的订单会存在已下单、待支付、待发货、待收货等状态,这些状态会通过不同的行为进行相互转换,这时候在系统设计时就可以使用状态模式。


下面是状态模式类图:



可以看到状态模式主要包含三种类型的角色:


1、上下文( Context )角色:封装了状态的实例,负责维护状态实例,并将请求委托给当前的状态对象。


2、抽象状态( State )角色:定义了表示不同状态的接口,并封装了该状态下的行为。所有具体状态都实现这个接口。


3、具体状态( Concrete State )角色:具体实现了抽象状态角色的接口,并封装了该状态下的行为。


下面是使用状态模式实现红绿灯状态变更的一个简单案例:


抽象状态类:


/** * @description: 抽象状态类 */public abstract class MyState {    abstract void handler();}
复制代码


具体状态类 A


/** * @description: 具体状态A */public class RedLightState extends MyState{
@Override void handler() { System.out.println("红灯停"); }}
复制代码


具体状态类 B


/** * @description: 具体状态B */public class GreenLightState extends MyState{
@Override void handler() { System.out.println("绿灯行"); }}
复制代码


环境类:维护当前状态对象,并提供了切换状态的方法。


/** * @description: 环境类 */public class MyContext {
private MyState state;
public void setState(MyState state) { this.state = state; }
public void handler() { state.handler(); }}
复制代码


测试类


/** * @description: 测试状态模式 */public class TestStateModel {    public static void main(String[] args) {        MyContext myContext = new MyContext();
RedLightState redLightState = new RedLightState(); GreenLightState greenLightState = new GreenLightState();
myContext.setState(redLightState); myContext.handler(); //红灯停
myContext.setState(greenLightState); myContext.handler(); //绿灯行 }}
复制代码


下面是对应的执行结果



可以发现,使用状态模式中的状态类在一定程度上也消除了 if-else 逻辑校验,看到这里, 有些人可能会有疑问:状态模式和策略模式的区别是什么呢?


状态模式更关注对象在不同状态的行为和状态之间的流转,而策略模式更关注对象不同策略的选择。


上面我们介绍了设计模式中的状态模式,接下来我们来看看 Spring 状态机。

2. Spring 状态机

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,就是指一张状态转换图。状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring 也提供了一个很好的解决方案。Spring 中的组件名称就叫作状态机(StateMachine)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。


通过定义,我们很容易分析得到状态机应当具备一下几个要素:


1.当前状态:也就是状态流转的起始状态。


2.触发事件: 引起状态之间流转的一些列动作。


3.响应函数: 触发事件到下一个状态之间的规则。


4.目标状态:状态流转的目标状态。


对于组件化的状态机,当前使用较多的主要是两种:一种是 Spring 状态机,一种是 COLA 状态机,这两种状态机的对比如下表所示:



可以看到,Spring 状态机锁提供的内容较为丰富,当然对于自定义的支持就不如 COLA 状态机好,如果对自定义的需求比较高,那建议使用 COLA 状态机。


本文以 Spring 状态机为例,展示如何在业务系统中使用状态机。


为了便于大家了解 Spring 状态机的实现原理和使用方式以及其提供的功能,下面列出了官方文档和源码,感兴趣的同学可以阅读阅读。


官方文档: https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-states


源代码: https://github.com/spring-projects/spring-statemachine

3. Spring 状态机实现订单状态流转

对于状态模式,Spring 封装好了一个组件,就叫状态机(StateMachine)。Spring 状态机可以帮助我们开发者简化状态控制的开发过程,让状态机结构更加层次化。下面用 Spring 状态机模拟一个订单状态流转的过程。


3.1 环境准备


首先,如果要使用 spring 状态机,需要引入对应的 jar 包,这里我的 springboot 版本是:2.2.1.RELEASE


<dependency>    <groupId>org.springframework.statemachine</groupId>    <artifactId>spring-statemachine-core</artifactId>    <version>${springboot.version}</version></dependency>
复制代码


下面是简化的订单的定义,以及订单状态和订单转换行为的枚举


/** * @description: 模拟订单类 */@Datapublic class Order {    private Long orderId;    private OrderStatusEnum orderStatus;}
/** * @description: 订单状态 */public enum OrderStatusEnum { // 待支付 WAIT_PAYMENT, // 待发货 WAIT_DELIVER, // 待收货 WAIT_RECEIVE, // 完成 FINISH;}
/** * @description:订单状态转换行为 */public enum OrderStatusChangeEventEnum { //支付 PAYED, //发货 DELIVERY, //收货 RECEIVED;}
复制代码


3.2 构造订单状态机


在引入 jar 包之后,需要构建一个针对订单状态流转的状态机


订单状态机配置类如下:


/** * @description: 订单状态机 */@Configuration@EnableStateMachinepublic class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {
/** * 配置状态 */ @Override public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception { states.withStates() .initial(OrderStatusEnum.WAIT_PAYMENT) .end(OrderStatusEnum.FINISH) .states(EnumSet.allOf(OrderStatusEnum.class)); }
/** * 配置状态转换事件关系 */ @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); }}
复制代码


3.3 编写状态机监听器


监听状态变更事件,完成状态转换。


/** * @description: 状态监听 */@Component@WithStateMachine@Transactionalpublic 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("支付,状态机反馈信息:" + 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("发货,状态机反馈信息:" + 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("收货,状态机反馈信息:" + message.getHeaders().toString()); return true; }
}
复制代码


3.4 编写订单服务类


模拟对订单的一些业务操作


/** * @description: 订单服务 */@Servicepublic class OrderServiceImpl implements OrderService {
@Resource private StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;
private long id = 1L;
private Map<Long, Order> orders = Maps.newConcurrentMap();
@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.toString()); return order; }
@Override public Order pay(long id) { Order order = orders.get(id); System.out.println("尝试支付,订单号:" + id); Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED). setHeader("order", order).build(); if (!sendEvent(message)) { System.out.println(" 支付失败, 状态异常,订单号:" + id); } return orders.get(id); }
@Override public Order deliver(long id) { Order order = orders.get(id); System.out.println(" 尝试发货,订单号:" + id); if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY) .setHeader("order", order).build())) { System.out.println(" 发货失败,状态异常,订单号:" + id); } return orders.get(id); }
@Override public Order receive(long id) { Order order = orders.get(id); System.out.println(" 尝试收货,订单号:" + id); if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED) .setHeader("order", order).build())) { System.out.println(" 收货失败,状态异常,订单号:" + id); } return orders.get(id); }

@Override public Map<Long, Order> getOrders() { return orders; }
/** * 发送状态转换事件 * @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; }}
复制代码


3.5 测试入口


这里编写一个 controller 模拟 c 端用户请求,为了便于展示,这里使用一个测试方法完成所有的操作


@RestControllerpublic class OrderController {
@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("全部订单状态:" + orderService.getOrders()); return "success"; }
}
复制代码


下面是对应的执行结果



可以看到 spring 状态机很好的控制了订单在各个状态之间的流转。

4. 思考与总结

思考:针对状态机的特点,还有其他思路实现一个状态机吗?下面是一些常规思路,如果还有其他方法欢迎在评论区留言。


1. 消息队列方式


订单状态的流转可以通过 MQ 发布一个事件,消费者根据业务条件把订单状态进行流转,可以根据不同的事件发送到不同的 Topic。


2. 定时任务驱动


每隔一段时间启动一下 job,根据特定的状态从数据库中拿对应的订单记录,然后判断订单是否有条件到达下一个状态。


3. 规则引擎方式


业务团队可以在规则引擎里编写一系列的状态及其对应的转换规则,由规则引擎根据已经加载的规则对输入数据进行解析,根据解析的结果执行相应的动作,完成状态流转。


总结:


本文主要介绍了设计模式中的状态模式,并在此基础上介绍了 Spring 状态机相关的概念,并根据常见的订单流转场景,介绍了 Spring 状态机的使用方式。文中如有不当之处,欢迎在评论区批评指正。

5. 参考内容

https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-states


https://cloud.tencent.com/developer/article/2198477?areaId=106001


https://cloud.tencent.com/developer/article/2360708?areaId=106001


https://juejin.cn/post/7087064901553750030


https://my.oschina.net/u/4090830/blog/10092135


https://juejin.cn/post/7267506576448929811

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
玩转Spring状态机_京东科技开发者_InfoQ写作社区