玩转Spring状态机 | 京东云技术团队

2024-03-19 986 0

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

1. 状态模式

状态模式的定义如下:

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

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

下面是状态模式类图:


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

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

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

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

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

抽象状态类:

/**
 * @description: 抽象状态类
 */publicabstractclassMyState{abstractvoidhandler();}

具体状态类A

/**
 * @description: 具体状态A
 */publicclassRedLightStateextendsMyState{@Overridevoidhandler(){System.out.println("红灯停");}}

具体状态类B

/**
 * @description: 具体状态B
 */publicclassGreenLightStateextendsMyState{@Overridevoidhandler(){System.out.println("绿灯行");}}

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

/**
 * @description: 环境类
 */publicclassMyContext{privateMyStatestate;publicvoidsetState(MyStatestate){this.state =state;}publicvoidhandler(){state.handler();}}

测试类

/**
 * @description: 测试状态模式
 */publicclassTestStateModel{publicstaticvoidmain(String[]args){MyContextmyContext =newMyContext();RedLightStateredLightState =newRedLightState();GreenLightStategreenLightState =newGreenLightState();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 状态机
API 调用 使用 Reactive 的 Mono、Flux 方式进行 API 调用 同步的 API 调用,如果有需要也可以将方法通过 消息队列、定时任务、多线程等方式进行异步调用
代码量 core 包 284 个接口和类 36 个接口和类
生态 非常丰富 较为贫瘠
定制化难度 困难 简单

可以看到,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: 模拟订单类
 */@DatapublicclassOrder{privateLongorderId;privateOrderStatusEnumorderStatus;}/**
 * @description: 订单状态
 */publicenumOrderStatusEnum{// 待支付WAIT_PAYMENT,// 待发货WAIT_DELIVER,// 待收货WAIT_RECEIVE,// 完成FINISH;}/**
 * @description:订单状态转换行为
 */publicenumOrderStatusChangeEventEnum{//支付PAYED,//发货DELIVERY,//收货RECEIVED;}

3.2 构造订单状态机

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

订单状态机配置类如下:

/**
 * @description: 订单状态机
 */@Configuration@EnableStateMachinepublicclassOrderStatusMachineConfigextendsStateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum>{/**
     * 配置状态
     */@Overridepublicvoidconfigure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum>states)throwsException{states.withStates().initial(OrderStatusEnum.WAIT_PAYMENT).end(OrderStatusEnum.FINISH).states(EnumSet.allOf(OrderStatusEnum.class));}/**
     * 配置状态转换事件关系
     */@Overridepublicvoidconfigure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum>transitions)throwsException{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@TransactionalpublicclassOrderStatusListener{@OnTransition(source ="WAIT_PAYMENT",target ="WAIT_DELIVER")publicbooleanpayTransition(Messagemessage){Orderorder =(Order)message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);System.out.println("支付,状态机反馈信息:"+message.getHeaders().toString());returntrue;}@OnTransition(source ="WAIT_DELIVER",target ="WAIT_RECEIVE")publicbooleandeliverTransition(Messagemessage){Orderorder =(Order)message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);System.out.println("发货,状态机反馈信息:"+message.getHeaders().toString());returntrue;}@OnTransition(source ="WAIT_RECEIVE",target ="FINISH")publicbooleanreceiveTransition(Messagemessage){Orderorder =(Order)message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.FINISH);System.out.println("收货,状态机反馈信息:"+message.getHeaders().toString());returntrue;}}

3.4 编写订单服务类

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

/**
 * @description: 订单服务
 */@ServicepublicclassOrderServiceImplimplementsOrderService{@ResourceprivateStateMachine<OrderStatusEnum, OrderStatusChangeEventEnum>orderStateMachine;privatelongid =1L;privateMap<Long, Order>orders =Maps.newConcurrentMap();@OverridepublicOrdercreate(){Orderorder =newOrder();order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);order.setOrderId(id++);orders.put(order.getOrderId(),order);System.out.println("订单创建成功:"+order.toString());returnorder;}@OverridepublicOrderpay(longid){Orderorder =orders.get(id);System.out.println("尝试支付,订单号:"+id);Messagemessage =MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).setHeader("order",order).build();if(!sendEvent(message)){System.out.println(" 支付失败, 状态异常,订单号:"+id);}returnorders.get(id);}@OverridepublicOrderdeliver(longid){Orderorder =orders.get(id);System.out.println(" 尝试发货,订单号:"+id);if(!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY).setHeader("order",order).build())){System.out.println(" 发货失败,状态异常,订单号:"+id);}returnorders.get(id);}@OverridepublicOrderreceive(longid){Orderorder =orders.get(id);System.out.println(" 尝试收货,订单号:"+id);if(!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED).setHeader("order",order).build())){System.out.println(" 收货失败,状态异常,订单号:"+id);}returnorders.get(id);}@OverridepublicMap<Long, Order>getOrders(){returnorders;}/**
     * 发送状态转换事件
     * @param message
     * @return
     */privatesynchronizedbooleansendEvent(Message<OrderStatusChangeEventEnum>message){booleanresult =false;try{orderStateMachine.start();result =orderStateMachine.sendEvent(message);}catch(Exceptione){e.printStackTrace();}finally{if(Objects.nonNull(message)){Orderorder =(Order)message.getHeaders().get("order");if(Objects.nonNull(order)&&Objects.equals(order.getOrderStatus(),OrderStatusEnum.FINISH)){orderStateMachine.stop();}}}returnresult;}}

3.5 测试入口

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

@RestControllerpublicclassOrderController{@ResourceprivateOrderServiceorderService;@RequestMapping("/testOrderStatusChange")publicStringtestOrderStatusChange(){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

作者:京东科技 孙扬威

来源:京东云开发者社区 转载请注明来源


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

靶场战神为何会陨落?
漏洞分析与实战分享 | 深入探讨pkexec提权漏洞
postMessage常见安全问题
[Meachines] [Medium] Lightweight LDAP密码嗅探+TRP00F 自…
ofcms V1.2 代码审计(一)
网安内家拳——东西向网络安全是门高级功夫

发布评论