上一篇說了不少廢話,這一篇就不嘮叨,先跑起來java
一、來個spring boot
去start.spring.io新建一個springboot的項目,雖然我對spirngboot也有很多的牢騷,但做爲demo的開始,仍是一個很好用的腳手架,記得選spring statemachine,爲了方便,我還選了web 模塊git
點擊generate project 下載到本地,用IDE打開,順便說一句,我用的是java IDE界逼格很低的eclipse,由於我一直用它,還不要錢。web
二、跑起來一個廢物例子spring
在本地打開後咱們首先看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 // 收貨 }
還有個OrderSingleEventConfigspringboot
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來運行,你們見諒app
@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的日誌eclipse
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
三、咱們來說一下這個例子ide
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());
至此,狀態機就跑起來了,謝謝你們,本教程到此結束,有疑問我也沒辦法。
四、我是還不想結束的番外篇
現實的世界那有這麼簡單,這樣的一個例子在企業級的開發中毫無用處,你們能夠想一想這個例子有啥用,其實什麼問題都沒有解決。你們能想到有哪些問題呢,我用我淺薄的開發經驗一眼就看到了如下幾個問題: 1)整個項目只有一種狀態機流程,我要是想在一個項目裏面又有訂單流程,又有公文審批流程怎麼辦,難道和老闆講個人狀態機demo告訴我,狀態機的流程只能選一個? 2)整個項目只有一個狀態機流程,你沒有看眼花,這是另一個問題。哪怕是隻有一種流程,好比訂單流程,其實也是有不少訂單的流程在同時跑,而不是像這個例子,所有訂單共用一個流程,一個訂單到WAITING_FOR_RECEIVE狀態了,其餘訂單就不能是UNPAY狀態了。 3)參數問題,咱們作項目,不是爲了看日誌打出「---訂單建立,待支付---」給咱們玩的,而是要處理具體業務的,拿訂單流程來講吧,訂單號怎麼傳,狀態改變時間怎麼回數據庫,等等問題其實都須要解決。 4)存儲問題,狀態機若是有多個,須要的時候從哪去,暫時不須要了放哪去,這都是問題,因此存儲狀態機也是須要解決的。 下一章教程,咱們帶着這四個靈魂問題繼續咱們的學習之旅