上一篇说了很多废话,这一篇就不唠叨,先跑起来
1、来个spring boot
去 start.spring.io 新建一个springboot的项目,虽然我对spirngboot也有不少的牢骚,但作为demo的开始,还是一个很好用的脚手架,记得选spring statemachine,为了方便,我还选了web 模块
点击generate project 下载到本地,用IDE打开,顺便说一句,我用的是java IDE界逼格很低的eclipse,因为我一直用它,还不要钱。
2、跑起来一个废物例子
在本地打开后我们首先看pom.xml文件,里面和我们相关的有这几段
<properties> <spring-statemachine.version>2.0.1.RELEASE</spring-statemachine.version> </properties> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-bom</artifactId> <version>${spring-statemachine.version}</version> <type>pom</type> <scope>import</scope> </dependency>
现在就可以在springboot里面用statemachine了,然后我们就开始想办法跑起来。 先来一个StateMachineConfig,它的主要作用就告诉状态机的初始状态应该啥样,然后把整个状态流程都用代码配置出来。@Configuration是springboot的注解,表示这个类负责配置,@EnableStateMachine表示这个配置类是用在spring statemachine上面的。
package com.skyblue.statemachine.config; import java.util.EnumSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; @Configuration @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> { private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception { states.withStates().initial(OrderStates.UNPAID).states(EnumSet.allOf(OrderStates.class)); } @Override public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception { transitions.withExternal().source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_RECEIVE).event(OrderEvents.PAY).and() .withExternal().source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE).event(OrderEvents.RECEIVE); } }
它配套需要OrderStates和OrderEvents,代码如下:
package com.skyblue.statemachine.config; public enum OrderStates { UNPAID, // 待支付 WAITING_FOR_RECEIVE, // 待收货 DONE // 结束 } package com.skyblue.statemachine.config; public enum OrderEvents { PAY, // 支付 RECEIVE // 收货 }
还有个OrderSingleEventConfig
package com.skyblue.statemachine.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.Message; import org.springframework.statemachine.annotation.OnTransition; import org.springframework.statemachine.annotation.WithStateMachine; @WithStateMachine(name="orderSingleMachine") public class OrderSingleEventConfig { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 当前状态UNPAID */ @OnTransition(target = "UNPAID") public void create() { logger.info("---订单创建,待支付---"); } /** * UNPAID->WAITING_FOR_RECEIVE 执行的动作 */ @OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE") public void pay() { logger.info("---用户完成支付,待收货---"); } /** * WAITING_FOR_RECEIVE->DONE 执行的动作 */ @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE") public void receive() { logger.info("---用户已收货,订单完成---"); } }
因为我本人不会用单元测试,我用了一个controller来运行,大家见谅
@RestController @RequestMapping("/statemachine") public class StateMachineController { @Autowired private StateMachine orderSingleMachine; @RequestMapping("/testSingleOrderState") public void testSingleOrderState() throws Exception { // 创建流程 orderSingleMachine.start(); // 触发PAY事件 orderSingleMachine.sendEvent(OrderEvents.PAY); // 触发RECEIVE事件 orderSingleMachine.sendEvent(OrderEvents.RECEIVE); // 获取最终状态 System.out.println("最终状态:" + orderSingleMachine.getState().getId()); } }
访问页面 http://localhost :port/statemachine/testSingleOrderState,页面没有变化,我们看console的日志
2019-04-25 19:14:11.782 INFO 17020 --- [nio-9991-exec-3] tConfig$$EnhancerBySpringCGLIB$$ab30f59f : ---订单创建,待支付--- 2019-04-25 19:14:11.787 INFO 17020 --- [nio-9991-exec-3] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@2648176e 2019-04-25 19:14:11.787 INFO 17020 --- [nio-9991-exec-3] o.s.s.support.LifecycleObjectSupport : started UNPAID DONE WAITING_FOR_RECEIVE / UNPAID / uuid=93e4f752-55bc-40ef-84e4-6c00cf5a4fc5 / id=null 2019-04-25 19:14:11.797 INFO 17020 --- [nio-9991-exec-3] tConfig$$EnhancerBySpringCGLIB$$ab30f59f : ---用户完成支付,待收货--- 2019-04-25 19:14:11.800 INFO 17020 --- [nio-9991-exec-3] tConfig$$EnhancerBySpringCGLIB$$ab30f59f : ---用户已收货,订单完成--- 最终状态:DONE
3、我们来讲一下这个例子
1)描述上图
其实OrderStates表达的就是这张图的状态(state),OrderEvents表达的就是这张图状态间的事件(event),我们的业务代码就是要塞到事件(event)里面去,处理在状态转换间要处理的事情,比如P从UNPAID到WAITING_FOR_RECEIVE中间的PAY事件(event),我们就可能需要调用支付接口,或者判断用户的会员等级是不是有支付优惠啥的。但state和event是指描述这个流程的三个点和两条线,具体的流程指向要怎么描述呢,就轮到StateMachineConfig出场了。StateMachineConfig继承了EnumStateMachineConfigurerAdapter类,表明身份,我就是来配置状态机的初始状态,并描绘一下状态流程的全过程。
2)塞入业务代码
现在我们知道状态(state)和事件(event)了,也描绘了这个状态机的流程和初始状态是什么样了,然后我们要做什么,当然是开始把业务代码塞到事件(event)里面去,于是OrderSingleEventConfig登场了。OrderSingleEventConfig里面的create,pay和receive方法就是描绘事件触发时需要做什么,但这三个方法名其实是可以自己随便写的(当然最好和event名一样,避免一年后自己看代码时骂当年自己为什么那么蠢,至于别人阅读你的代码嘛......业务代码谁要看你的,别人会重写的),真正和上面描绘的状态流程对应的是@OnTransition,source代表现在的状态,target代表目标状态,很容易懂的。
3)运行状态机
我们在需要的地方引入一个状态机
@Autowired
private StateMachine orderSingleMachine;
然后运行就可以啦
// 创建流程
orderSingleMachine.start(); // 触发PAY事件 orderSingleMachine.sendEvent(OrderEvents.PAY); // 触发RECEIVE事件 orderSingleMachine.sendEvent(OrderEvents.RECEIVE); // 获取最终状态 System.out.println("最终状态:" + orderSingleMachine.getState().getId());
至此,状态机就跑起来了,谢谢大家,本教程到此结束,有疑问我也没办法。
4、我是还不想结束的番外篇
现实的世界那有这么简单,这样的一个例子在企业级的开发中毫无用处,大家可以想想这个例子有啥用,其实什么问题都没有解决。大家能想到有哪些问题呢,我用我浅薄的开发经验一眼就看到了以下几个问题: 1)整个项目只有一种状态机流程,我要是想在一个项目里面又有订单流程,又有公文审批流程怎么办,难道和老板讲我的状态机demo告诉我,状态机的流程只能选一个? 2)整个项目只有一个状态机流程,你没有看眼花,这是另外一个问题。哪怕是只有一种流程,比如订单流程,其实也是有很多订单的流程在同时跑,而不是像这个例子,全部订单共用一个流程,一个订单到WAITING_FOR_RECEIVE状态了,其他订单就不能是UNPAY状态了。 3)参数问题,我们做项目,不是为了看日志打出“---订单创建,待支付---”给我们玩的,而是要处理具体业务的,拿订单流程来说吧,订单号怎么传,状态改变时间怎么回数据库,等等问题其实都需要解决。 4)存储问题,状态机如果有多个,需要的时候从哪去,暂时不需要了放哪去,这都是问题,所以存储状态机也是需要解决的。 下一章教程,我们带着这四个灵魂问题继续我们的学习之旅
配套代码地址