Spring Statemachine 简介

Spring Statemachine是Spring官方提供的一个框架,供应用程序开发人员在Spring应用程序中使用状态机。支持状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。

官网地址:https://projects.spring.io/spring-statemachine/

本文使用版本:2.2.0.RELEASE

Spring Statemachine 项目模块

使用 Spring Statemachine

Spring Statemachine 的设计是有状态的,每个statemachine实例内部保存了当前状态机上下文相关的属性(比如当前状态、上一次状态等),因此是线程不安全的。因此,在实际场景中,基本上都是利用工厂创建状态机。

配置 Spring Statemachine 工厂

方式一 通过Adapter构建工厂

@Configuration
@EnableStateMachineFactory
public class Config6extends EnumStateMachineConfigurerAdapter<States, Events> {@Overridepublic void configure(StateMachineStateConfigurer<States, Events> states)throws Exception {states.withStates().initial(States.S1).end(States.SF).states(EnumSet.allOf(States.class));}}public class Bean3 {@AutowiredStateMachineFactory<States, Events> factory;void method() {StateMachine<States,Events> stateMachine = factory.getStateMachine();stateMachine.start();}
}

缺点:依赖@Configuration注解和Spring应用上下文,即编译时就需要指定状态机配置。

方式二 通过Builder构建工厂【推荐】

StateMachine<String, String> buildMachine1() throws Exception {Builder<String, String> builder = StateMachineBuilder.builder();builder.configureStates().withStates().initial("S1").end("SF").states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4")));return builder.build();
}

监听状态机事件

方式一 Spring 上下文事件机制

状态机中的事件类OnTransitionStartEvent、OnTransitionEvent、OnTransitionEndEvent、OnStateExiteEvent、OnStateEntryEvent、OnStateChangeEvent、OnStateMachineStart、OnStateMachineStop等都是 ApplicationEvent 的子类,因此可以用Spring 中的ApplicationListener。

public class StateMachineApplicationEventListenerimplements ApplicationListener<StateMachineEvent> {@Overridepublic void onApplicationEvent(StateMachineEvent event) {//监听事件处理逻辑}
}@Configuration
public class ListenerConfig {@Beanpublic StateMachineApplicationEventListener contextListener() {return new StateMachineApplicationEventListener();}
}

缺点:Spring Statemachine官方认为Spring ApplicationContext 并不是一个很快的事件总线。从提升性能方面考虑,推荐使用下面的StateMachineListener方式。

方式二 使用StateMachineListener【推荐】

StateMachineListener 是一个接口,通常使用其实现类StateMachineListenerAdapter:

public class StateMachineListenerAdapter<S, E> implements StateMachineListener<S, E> {@Overridepublic void stateChanged(State<S, E> from, State<S, E> to) {}@Overridepublic void stateEntered(State<S, E> state) {}@Overridepublic void stateExited(State<S, E> state) {}@Overridepublic void eventNotAccepted(Message<E> event) {}@Overridepublic void transition(Transition<S, E> transition) {}@Overridepublic void transitionStarted(Transition<S, E> transition) {}@Overridepublic void transitionEnded(Transition<S, E> transition) {}@Overridepublic void stateMachineStarted(StateMachine<S, E> stateMachine) {}@Overridepublic void stateMachineStopped(StateMachine<S, E> stateMachine) {}@Overridepublic void stateMachineError(StateMachine<S, E> stateMachine, Exception exception) {}@Overridepublic void extendedStateChanged(Object key, Object value) {}@Overridepublic void stateContext(StateContext<S, E> stateContext) {}}

使用方式:

1、状态机实例添加Listener:

public class Config7 {@AutowiredStateMachine<States, Events> stateMachine;@Beanpublic StateMachineEventListener stateMachineEventListener() {StateMachineEventListener listener = new StateMachineEventListener();stateMachine.addStateListener(listener);return listener;}}

2、构建工厂时指定Listener:

 public StateMachine<OrderStates, Events> build() throws Exception {StateMachineBuilder.Builder<OrderStates, Events> builder = StateMachineBuilder.builder();System.out.println("构建订单状态机");builder.configureConfiguration().withConfiguration().beanFactory(beanFactory).machineId("orderMachineId").listener(listener());builder.configureStates().withStates().initial(OrderStates.WAIT_PAY).states(EnumSet.allOf(OrderStates.class));

与应用程序交互

通过上述监听状态机的事件或使用具有状态和转换的动作来与状态机进行交互有点受限制。 有时,这种方法过于局限和冗长,无法与状态机所使用的应用程序进行交互。 对于此特定用例,我们进行了Spring样式的上下文集成,可轻松将状态机功能插入到bean中。

使用:

@WithStateMachine
public class Bean5 {@OnTransition(source = "S1", target = "S2")public void fromS1ToS2() {}@OnTransitionpublic void anyTransition() {}@StatesOnTransition(source = States.S1, target = States.S2)public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {}@OnTransitionpublic void anyTransition(@EventHeaders Map<String, Object> headers,@EventHeader("myheader1") Object myheader1,@EventHeader(name = "myheader2", required = false) String myheader2,ExtendedState extendedState,StateMachine<String, String> stateMachine,Message<String> message,Exception e) {}@OnStateChangedpublic void anyStateChange() {}@OnEventNotAcceptedpublic void anyEventNotAccepted() {}@OnEventNotAccepted(event = "E1")public void e1EventNotAccepted() {}//.......
}

发送事件时如何传递业务参数?

StateMachine中有个方法,发送事件时可以传递业务参数:

 /*** Send an event {@code E} wrapped with a {@link Message} to the region.** @param event the wrapped event to send* @return true if event was accepted*/boolean sendEvent(Message<E> event);

Message 是spring-messaging 模块中的,Spring Statemachine利用它承载事件和业务参数:

使用示例:

   @GetMapping("/order/pay")public String payOrder(@RequestParam String orderNo){OrderDO orderDO = orderMapper.selectByOrderNo(orderNo);try {StateMachine<OrderStates, Events> stateMachine = orderStateMachineBuilder.build();stateMachine.start();Message<Events> message = MessageBuilder.withPayload(Events.PAY).setHeader("orderDO", orderDO).setHeader("payChannel", "AliPay").build();stateMachine.sendEvent(message);} catch (Exception e) {e.printStackTrace();}return "pay success !";}

在 @WithStateMachine 注解的类中,可以接收 Message ,获取事件以及业务参数用于做业务处理逻辑:
@WithStateMachine(id = "orderMachineId")
public class StateMachineIntegration {@AutowiredOrderMapper orderMapper;@OnTransition(target = "WAIT_PAY")public void create(){}@OnTransition(source = "WAIT_PAY", target= "PAID")@Transactional(rollbackFor = Exception.class)public void pay(Message<Events> message) {OrderDO orderDO = (OrderDO) message.getHeaders().get("orderDO");System.out.println("传递的orderDO:" + orderDO);String payChannel = (String) message.getHeaders().get("payChannel");System.out.println("传递的payChannel:" + payChannel);orderDO.setOrderStatus(OrderStates.PAID.getCode());orderMapper.update(orderDO);}
}

设置状态机当前状态

上面的例子状态机都是从初始状态开始流转的,但是实际业务,有的订单在“WAIT_PAY”(待支付)状态、有的订单在“PAID”(待发货)状态,还有的订单在“WAIT_RECEIVE”(待收货)状态等等,因此需要根据订单当前状态设置状态机当前状态。

Spring Statemachine 持久化

如果不设置状态机当前状态,可以选择持久化状态机实例。Spring Statemachine 支持把状态机实例存储到内存、NoSQL、关系型数据库中,这样在使用时把存储的状态机实例恢复即可使用。

缺点

1、存储在内存中,占用业务应用程序的内存显然不可取;

2、存储在Redis中,Redis 不一定可靠,需要考虑 Redis 宕机之类的问题;

3、存储在数据库中,这给开发工作带来很大不便,开发者应该只关心保存业务数据,为什么要保存状态机实例,而且这也会占用数据库空间。

如何设置状态机当前状态

我们看到状态机的接口 StateMachinePersister<S,E,T>  支持根据持久化的状态机上下文 contextObj 重置状态机实例,下面的 restore 方法:

public interface StateMachinePersister<S, E, T> {/*** Persist a state machine with a given context object.** @param stateMachine the state machine* @param contextObj the context ojb* @throws Exception the exception in case or any persist error*/void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception;/*** Reset a state machine with a given context object.* Returned machine has been reseted and is ready to be used.** @param stateMachine the state machine* @param contextObj the context ojb* @return the state machine* @throws Exception the exception in case or any persist error*/StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception;
}

我们看一下Spring StateMachine 中 AbstractStateMachinePersister 类中的实现:

@Override
public final StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception {final StateMachineContext<S, E> context = stateMachinePersist.read(contextObj);stateMachine.stop();stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {@Overridepublic void apply(StateMachineAccess<S, E> function) {function.resetStateMachine(context);}});stateMachine.start();return stateMachine;
}

这里面有一个接口 StateMachineAccess<S,E>,看一下它的定义:

public interface StateMachineAccess<S, E> {/*** Reset state machine.* * @param stateMachineContext the state machine context*/void resetStateMachine(StateMachineContext<S, E> stateMachineContext);//......
}

它有一个方法 resetStateMachine(StateMachineContext<S,E> stateMachineContext),通过传入构造的状态机上下文 stateMachineContext,可以做到设置状态机的当前状态。于是,我们可以考虑根据订单当前状态构建一个 StateMachineContext 对象,根据 StateMachineContext 对象信息重置状态机的当前状态。

实际使用示例:

 /*** 利用StateMachineAccess<S, E>接口resetStateMachine方法,重新设置状态机上下文的方法是可以设置当前状态的* 其实反持久化接口 orderPersister.restore 方法内部就是这样恢复状态当前上下文的*/@GetMapping("/order/deliver3")public String deliverOrder3(@RequestParam String orderNo) throws Exception {OrderDO orderDO = orderMapper.selectByOrderNo(orderNo);StateMachine<OrderStates, Events> stateMachine = orderStateMachineBuilder.build();StateMachineContext<OrderStates, Events> stateMachineContext = new DefaultStateMachineContext<OrderStates, Events>(new ArrayList<>(), OrderStates.getByCode(orderDO.getOrderStatus()),null, null, null, null, stateMachine.getId());try {// 设置状态机实例当前状态stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.resetStateMachine(stateMachineContext));} catch (Exception e) {e.printStackTrace();}// 别忘了 start 状态机,否则也不会触发状态流转stateMachine.start();System.out.println("根据订单状态设置后的状态机当前状态:" + stateMachine.getState().getId());Message<Events> message = MessageBuilder.withPayload(Events.DELIVER).setHeader("orderDO", orderDO).setHeader("expressNo", "SF12435678").build();stateMachine.sendEvent(message);OrderStates orderState = stateMachine.getState().getId();System.out.println("处理后订单状态:" + orderState);orderDO.setOrderStatus(orderState.getCode());orderMapper.update(orderDO);return "deliver success !";}

在上述的代码中,我们查到订单后,根据订单当前状态构建了 StateMachineContext 接口的实现类 DefaultStateMachineContext 对象,把这个对象作为入参调用StateMachineAccess<S,E> 接口的resetStateMachine(StateMachineContext<S,E> stateMachineContext)方法,状态机的当前状态就已经重置为 orderDO 中的订单状态了,这种方式就避免了状态机实例的持久化问题。

以上共同的缺点:

在高并发系统中,上述无论哪种方式,每次请求都要先构造出一个状态机实例,如果构造状态机复杂,会影响系统的QPS,大量的状态机实例也可能会带来GC等问题。

总结:

个人来看,Spring Statemachine 目前在生产中运用尤其是高并发场景下使用的案例不多,迭代版本也较少,社区的活跃度比较低,GitHub上 Star 数尚不及另一款状态机松鼠状态机,但是也在系统设计方面提供了一种思路。在调研各种状态机之后,我们采取了基于Cola-StateMachine改造开发的cmt-statemachine,二者都是无状态、单例的。

拓展:

借鉴Cola-StateMachine的设计,相关文章:https://blog.csdn.net/significantfrank/article/details/104996419

基于Cola-StateMachine我们开发的cmt-statemachine:https://github.com/dsc-cmt/cmt-statemachine

Spring Statemachine 简介相关推荐

  1. Spring入门简介

    Spring概况 Spring的简介 Spring是一个轻量级的控制反转和面向切面的容器框架,它主要是为了解决企业应用开发的复杂性而诞生的: 目的:解决企业应用开发的复杂性 功能:使用基本的javaB ...

  2. Spring Boot 入门——Spring Boot 简介||微服务简介

    Spring Boot 入门 1.Spring Boot 简介 Spring Boot来简化Spring应用开发,约定大于配置, 去繁从简,just run就能创建一个独立的,产品级别的应用 简化Sp ...

  3. Spring框架简介

    Spring框架简介 Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本. ...

  4. spring statemachine的企业可用级开发指南1-说些废话

    2019独角兽企业重金招聘Python工程师标准>>> 1.背景 在我打算学习spring statemachine的时候,我几乎看过了所有网上的中文教程,基本上都处于浅尝辄止的阶段 ...

  5. Spring StateMachine,教你快速实现一个状态机

    来源:http://t.cn/RIxCXiO Spring StateMachine框架可能对于大部分使用Spring的开发者来说还比较生僻,它的主要功能是帮助开发者简化状态机的开发过程,让状态机结构 ...

  6. Spring AOP 简介以及简单用法

    Spring AOP 简介以及简单用法 如果你去面试java开发, 那么Spring的AOP和DI几乎是必问的问题. 那么AOP是什么呢? 一. AOP 所谓Aop就是 Aspect-Oriented ...

  7. Spring Statemachine TODO

    为什么80%的码农都做不了架构师?>>>    编者注 之前自己实现的完整的有限状态机,具有事件能够传值的特点,个人很喜欢.最近又需要实现有限状态机,对于值传递没有要求,则可以使用通 ...

  8. 一、spring mvc简介

    2019独角兽企业重金招聘Python工程师标准>>> 这里有一段摘自开涛spring mvc中关于spring mvc简介的一段文字: Spring Web MVC是一种基于Jav ...

  9. spring boot简介_Spring Boot简介

    spring boot简介 在本教程中,我们将看一下Spring Boot,看看它与Spring框架有何不同. 我们还将讨论Spring Boot提供的各种功能. 什么是Spring Boot? 在开 ...

最新文章

  1. 微服务架构转型需要关注的运维监控的技术和建议
  2. linux7怎么查看rsync状态,linux – Rsync显示单个文件的进度
  3. 关于《计算机程序的构造和解释》
  4. qq分享提示设备未授权_QQ帐号已经可以注销了,过去几天,第一批尝试的人已经放弃了!...
  5. python两列相乘_python – Pandas group by和sum两列
  6. mysql5.7 单机多实例_MySQL数据库 5.7.21单机多实例安装
  7. struts使用拦截器注解
  8. Git下载安装以及基本指令使用
  9. SDRAM 控制器(七)——控制模块
  10. 加速度随机游走_怎么才能形象的说明IMU的bias随机游走?
  11. Curator使用手册
  12. win10 桌面(Windows 资源管理器)卡死的根本解决办法
  13. 【Unity】【Android】问题记录
  14. 干支纪年法简便算法_初中历史四种纪年法,每一种都要掌握
  15. python bar图 百分比_matplotlib bar()实现百分比堆积柱状图
  16. 在java中如何输入_java如何输入
  17. 基于VUE的后台管理系统
  18. tomcat 如何查看tomcat版本及位数——tomcat笔记
  19. 免费天气预报插件jquery版本
  20. span内一连串英文字符不会自动换行

热门文章

  1. Hbase查询表大小的4个方式
  2. tutorabc怎么学英文?我来说说自己的真实感受.
  3. mybatis报错:前言中不允许有内容
  4. 计算机网络——使用广播信道的数据链路层
  5. 我的Fiddler书籍:《Fiddler抓包让数据无处可藏》《玩转Fiddler》
  6. 集电极开路输出和漏极开路输出
  7. 统计一行文本的单词个数(引用@浅哥 大佬)
  8. windows制作安装包的工具
  9. js es10 新特性
  10. QT中简单的emit使用