spring statemachine的企業可用級開發指南2-先跑起來

上一篇說了很多廢話,這一篇就不嘮叨,先跑起來

1、來個spring boot
https://start.spring.io/新建...,雖然我對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)存儲問題,狀態機如果有多個,需要的時候從哪去,暫時不需要了放哪去,這都是問題,所以存儲狀態機也是需要解決的。

下一章教程,我們帶着這四個靈魂問題繼續我們的學習之旅

配套代碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章