🍃【SpringBoot技術專題】「StateMachine」FSM狀態機設計及實現

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言介紹","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文主要介紹一下狀態機以及相關的一些概念。結合一個簡單的訂單狀態流程,示例怎樣在Springboot中集成","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Spring-statemachine","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"有限狀態機(Finite-state machine)","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"有限狀態機(英語:finite-state machine,縮寫:FSM),簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行爲的數學模型","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"應用FSM模型可以幫助對象生命週期的狀態的順序以及導致狀態變化的事件進行管理","attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"將狀態和事件控制從不同的業務Service方法的if else中抽離出來。FSM的應用範圍很廣,對於有複雜狀態流,擴展性要求比較高的場景都可以使用該模型。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是狀態機模型中的4個要素,即現態、條件、動作、次態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"現態:是指當前所處的狀態。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"條件:又稱爲“事件”。當一個條件被滿足,將會觸發一個動作,或者執行一次狀態的遷移。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"動作:條件滿足後執行的動作。動作執行完畢後,可以遷移到新的狀態,也可以仍舊保持原狀態。動作不是必需的,當條件滿足後,也可以不執行任何動作,直接遷移到新狀態","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"次態:條件滿足後要遷往的新狀態。“次態”是相對於“現態”而言的,“次態”一旦被激活,就轉變成新的“現態”了","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態機中,每個狀態有着相應的行爲,隨着行爲的觸發來切換狀態。其中一種做法是使用二維數組實現狀態機機制,其中橫座標表示行爲,縱座標表示狀態,具體的數值則表示當前的狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以登錄場景設計一個狀態機。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/99c662b20d04c5c97960a69c536ca2a9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設計一張狀態機表。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"橫軸是動作,縱軸是狀態","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/86/86fdb46868d59373fc0729073d8987b5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"此時它的二維數組,如下所示","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d888eeea41c2a0dd57e8fc84d862fba7.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,我們也可以通過狀態模式實現一個狀態機,狀態模式將每一個狀態封裝成獨立的類,具體行爲會隨着內部狀態而改變。狀態模式用類表示狀態,這樣我們就能通過切換類來方便地改變對象的狀態,避免了冗長的條件分支語句,","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓系統具有更好的靈活性和可擴展性。現在,我們定義一個狀態枚舉,其中包括未連接、已連接、註冊中、已註冊 4 種狀態。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5c/5cb27d013ad637721160c23b77ab80c4.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個環境類,它是實際上是真正擁有狀態的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/19/1928d70e68c6f2b56e4a84ef04c7bae3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態模式用類表示狀態,這樣就能通過切換類來方便地改變對象的狀態。我們定義幾個狀態類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/806af5c4e19a28b8147ce79d912e1a31.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/07/07a454e4d517c1205544ca5e8dd97b72.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1f/1f230c1942a14324f2bc5e9a5d2bc0f0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ee/ee92d6a676cfac6b09116421ca592f12.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意的是,如果某個行爲不會觸發狀態的變化,我們可以拋出一個 RuntimeException 異常。此外,調用時,通過環境類控制狀態的切換,如下所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/aa/aafd6ccce330b95a65076f7029d0efb3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring StateMachine 讓狀態機結構更加層次化,可以幫助開發者簡化狀態機的開發過程。現在,我們來用 Spring StateMachine 進行改造。修改 pom 文件,添加 Maven/gradle 依賴。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"xml"},"content":[{"type":"text","text":"dependencies {\n compile 'org.springframework.statemachine:spring-statemachine-core:1.2.7.RELEASE'\n} \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個狀態枚舉,其中包括未連接、已連接、註冊中、已註冊 4 種狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public enum RegStatusEnum {\n // 未連接\n UNCONNECTED,\n // 已連接\n CONNECTED,\n // 正在登錄\n LOGINING,\n // 登錄進系統\n LOGIN_INTO_SYSTEM;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義事件枚舉,事件的發生觸發狀態轉換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public enum RegEventEnum {\n // 連接\n CONNECT,\n // 開始登錄\n BEGIN_TO_LOGIN,\n // 登錄成功\n LOGIN_SUCCESS,\n // 登錄失敗\n LOGIN_FAILURE,\n // 註銷登錄\n LOGOUT;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置狀態機,通過註解打開狀態機功能。配置類一般要繼承EnumStateMachineConfigurerAdapter類,並且重寫一些configure方法以配置狀態機的初始狀態以及事件與狀態轉移的聯繫。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import static com.qyz.dp.state.events.RegEventEnum.BEGIN_TO_LOGIN;\nimport static com.qyz.dp.state.events.RegEventEnum.CONNECT;\nimport static com.qyz.dp.state.events.RegEventEnum.LOGIN_FAILURE;\nimport static com.qyz.dp.state.events.RegEventEnum.LOGIN_SUCCESS;\nimport static com.qyz.dp.state.events.RegEventEnum.LOGOUT;\nimport static com.qyz.dp.state.state.RegStatusEnum.CONNECTED;\nimport static com.qyz.dp.state.state.RegStatusEnum.LOGINING;\nimport static com.qyz.dp.state.state.RegStatusEnum.LOGIN_INTO_SYSTEM;\nimport static com.qyz.dp.state.state.RegStatusEnum.UNCONNECTED;\n\nimport java.util.EnumSet;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.statemachine.config.EnableStateMachine;\nimport org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;\nimport org.springframework.statemachine.config.builders.StateMachineStateConfigurer;\nimport org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;\n\nimport com.qyz.dp.state.events.RegEventEnum;\nimport com.qyz.dp.state.state.RegStatusEnum;\n\n@Configuration\n@EnableStateMachine // 開啓狀態機配置\npublic class StateMachineConfig extends EnumStateMachineConfigurerAdapter{\n\n /**\n * 配置狀態機狀態\n */\n @Override\n public void configure(StateMachineStateConfigurer states) throws Exception {\n states.withStates()\n // 初始化狀態機狀態\n .initial(RegStatusEnum.UNCONNECTED)\n // 指定狀態機的所有狀態\n .states(EnumSet.allOf(RegStatusEnum.class));\n }\n\n /**\n * 配置狀態機狀態轉換\n */\n @Override\n public void configure(StateMachineTransitionConfigurer transitions) throws Exception {\n // 1. connect UNCONNECTED -> CONNECTED\n transitions.withExternal()\n .source(UNCONNECTED)\n .target(CONNECTED)\n .event(CONNECT)\n // 2. beginToLogin CONNECTED -> LOGINING\n .and().withExternal()\n .source(CONNECTED)\n .target(LOGINING)\n .event(BEGIN_TO_LOGIN)\n // 3. login failure LOGINING -> UNCONNECTED\n .and().withExternal()\n .source(LOGINING)\n .target(UNCONNECTED)\n .event(LOGIN_FAILURE)\n // 4. login success LOGINING -> LOGIN_INTO_SYSTEM\n .and().withExternal()\n .source(LOGINING)\n .target(LOGIN_INTO_SYSTEM)\n .event(LOGIN_SUCCESS)\n // 5. logout LOGIN_INTO_SYSTEM -> UNCONNECTED\n .and().withExternal()\n .source(LOGIN_INTO_SYSTEM)\n .target(UNCONNECTED)\n .event(LOGOUT);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring StateMachine 提供了註解配置實現方式,所有 StateMachineListener 接口中定義的事件都能通過註解的方式來進行配置實現。這裏以連接事件爲案例,@OnTransition 中 source 指定原始狀態,target 指定目標狀態,當事件觸發時將會被監聽到從而調用 connect() 方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在啓動springboot時,需要注入狀態機的狀態,事件的配置。起主要涉及到以下兩個類:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"StateMachineStateConfigurer < S, E> 配置狀態集合以及初始狀態,泛型參數S代表狀態,E代表事件。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"StateMachineTransitionConfigurer 配置狀態流的轉移,可以定義狀態轉換接受的事件。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置事件監聽器,事件發生時會觸發的操作","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import org.springframework.context.annotation.Configuration;\nimport org.springframework.statemachine.annotation.OnTransition;\nimport org.springframework.statemachine.annotation.WithStateMachine;\n\n@Configuration\n@WithStateMachine\npublic class StateMachineEventConfig {\n\n @OnTransition(source = \"UNCONNECTED\", target = \"CONNECTED\")\n public void connect() {\n System.out.println(\"Switch state from UNCONNECTED to CONNECTED: connect\");\n }\n\n @OnTransition(source = \"CONNECTED\", target = \"LOGINING\")\n public void beginToLogin() {\n System.out.println(\"Switch state from CONNECTED to LOGINING: beginToLogin\");\n }\n\n @OnTransition(source = \"LOGINING\", target = \"LOGIN_INTO_SYSTEM\")\n public void loginSuccess() {\n System.out.println(\"Switch state from LOGINING to LOGIN_INTO_SYSTEM: loginSuccess\");\n }\n\n @OnTransition(source = \"LOGINING\", target = \"UNCONNECTED\")\n public void loginFailure() {\n System.out.println(\"Switch state from LOGINING to UNCONNECTED: loginFailure\"); \n }\n \n @OnTransition(source = \"LOGIN_INTO_SYSTEM\", target = \"UNCONNECTED\")\n public void logout()\n {\n System.out.println(\"Switch state from LOGIN_INTO_SYSTEM to UNCONNECTED: logout\");\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過註解自動裝配一個狀態機這裏寫了一個rest接口來觸發狀態機變化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n@RestController\npublic class WebApi {\n\n @Autowired\n private StateMachine stateMachine;\n \n @GetMapping(value = \"/testStateMachine\")\n public void testStateMachine()\n {\n stateMachine.start();\n stateMachine.sendEvent(RegEventEnum.CONNECT);\n stateMachine.sendEvent(RegEventEnum.BEGIN_TO_LOGIN);\n stateMachine.sendEvent(RegEventEnum.LOGIN_FAILURE);\n stateMachine.sendEvent(RegEventEnum.LOGOUT);\n }\n}\nSwitch state from UNCONNECTED to CONNECTED: connect\nSwitch state from CONNECTED to LOGINING: beginToLogin\nSwitch state from LOGINING to UNCONNECTED: loginFailure\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從輸出可以看到,雖然send了4個事件,但只有三條輸出。原因是最後一個LOGOUT事件發生時,狀態機是UNCONNECTED狀態,沒有與LOGOUT事件關聯的狀態轉移,故不操作。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用spring實現的狀態機將類之間的關係全部交由了IOC容器做管理,實現了真正意義上的解耦。果然Spring大法好啊。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring StateMachine 讓狀態機結構更加層次化,我們來回顧下幾個核心步驟:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步,定義狀態枚舉。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步,定義事件枚舉。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三步,定義狀態機配置,設置初始狀態,以及狀態與事件之間的關係。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四步,定義狀態監聽器,當狀態變更時,觸發方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"狀態轉移的監聽器","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"狀態轉移過程中,可以通過監聽器(Listener)來處理一些持久化或者業務監控等任務。在需要持久化的場景中,可以在狀態機模式中的監聽器中添加持久化的處理。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"其中主要涉及到","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"StateMachineListener事件監聽器(通過Spring的event機制實現)。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"監聽stateEntered(進入狀態)、stateExited(離開狀態)、eventNotAccepted(事件無法響應)、transition(轉換)、transitionStarted(轉換開始)、transitionEnded(轉換結束)、stateMachineStarted(狀態機啓動)、stateMachineStopped(狀態機關閉)、stateMachineError(狀態機異常)等事件,藉助listener可以跟蹤狀態轉移。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"StateChangeInterceptor攔截器接口,不同於Listener。其可以改變狀態轉移鏈的變化。主要在preEvent(事件預處理)、preStateChange(狀態變更的前置處理)、postStateChange(狀態變更的後置處理)、preTransition(轉化的前置處理)、postTransition(轉化的後置處理)、stateMachineError(異常處理)等執行點生效。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"StateMachine 狀態機實例,spring statemachine支持單例、工廠模式兩種方式創建,每個statemachine有一個獨有的machineId用於標識machine實例;需要注意的是statemachine實例內部存儲了當前狀態機等上下文相關的屬性,因此這個實例不能夠被多線程共享。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了方便擴展更多的Listener,以及管理Listeners和Interceptors。可以定義一個基於狀態機實例的Handler: PersistStateMachineHandler,以及持久化實體的監聽器OrderPersistStateChangeListener如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"監聽器的Handler以及接口定義PersistStateMachineHandler:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class PersistStateMachineHandler extends LifecycleObjectSupport {\n\n private final StateMachine stateMachine;\n private final PersistingStateChangeInterceptor interceptor = new \n PersistingStateChangeInterceptor();\n private final CompositePersistStateChangeListener listeners = new \n CompositePersistStateChangeListener();\n\n /**\n * 實例化一個新的持久化狀態機Handler\n *\n * @param stateMachine 狀態機實例\n */\n public PersistStateMachineHandler(StateMachine \n stateMachine) {\n Assert.notNull(stateMachine, \"State machine must be set\");\n this.stateMachine = stateMachine;\n }\n\n @Override\n protected void onInit() throws Exception {\n stateMachine.getStateMachineAccessor().doWithAllRegions(function -> \n function.addStateMachineInterceptor(interceptor));\n }\n\n\n /**\n * 處理entity的事件\n *\n * @param event\n * @param state\n * @return 如果事件被接受處理,返回true\n */\n public boolean handleEventWithState(Message event, OrderStatus \n state) {\n stateMachine.stop();\n List> withAllRegions = \n stateMachine.getStateMachineAccessor()\n .withAllRegions();\n for (StateMachineAccess a : withAllRegions) {\n a.resetStateMachine(new DefaultStateMachineContext<>(state, null, null, null));\n }\n stateMachine.start();\n return stateMachine.sendEvent(event);\n }\n\n /**\n * 添加listener\n *\n * @param listener the listener\n */\n public void addPersistStateChangeListener(PersistStateChangeListener listener) {\n listeners.register(listener);\n }\n\n\n /**\n * 可以通過 addPersistStateChangeListener,增加當前Handler的PersistStateChangeListener。\n * 在狀態變化的持久化觸發時,會調用相應的實現了PersistStateChangeListener的Listener實例。\n */\n public interface PersistStateChangeListener {\n\n /**\n * 當狀態被持久化,調用此方法\n *\n * @param state\n * @param message\n * @param transition\n * @param stateMachine 狀態機實例\n */\n void onPersist(State state, \n Message message, Transition transition,\n StateMachine stateMachine);\n }\n\n\nprivate class PersistingStateChangeInterceptor extends \n StateMachineInterceptorAdapter {\n // 狀態預處理的攔截器方法\n @Override\n public void preStateChange(State state, \n Message message,\n Transition transition, \n StateMachine stateMachine) {\n listeners.onPersist(state, message, transition, stateMachine);\n }\n }\n\n private class CompositePersistStateChangeListener extends \n AbstractCompositeListener implements\n PersistStateChangeListener {\n @Override\n public void onPersist(State state, \n Message message,\n Transition transition, \n StateMachine stateMachine) {\n for (Iterator iterator = getListeners().reverse(); \n iterator.hasNext(); ) {\n PersistStateChangeListener listener = iterator.next();\n listener.onPersist(state, message, transition, stateMachine);\n }\n }\n }\n\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持久化狀態發生變化的訂單實體的Listener實現類OrderPersistStateChangeListener:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class OrderPersistStateChangeListener implements \n PersistStateMachineHandler.PersistStateChangeListener {\n\n @Autowired\n private OrderRepo repo;\n\n @Override\n public void onPersist(State state, \n Message message,\n Transition transition, \n StateMachine stateMachine) {\n if (message != null && message.getHeaders().containsKey(\"order\")) {\n Integer order = message.getHeaders().get(\"order\", Integer.class);\n Order o = repo.findByOrderId(order);\n OrderStatus status = state.getId();\n o.setStatus(status);\n repo.save(o);\n\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Springboot注入Handler和Listener bean的Configuration類,OrderPersistHandlerConfig","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\npublic class OrderPersistHandlerConfig {\n\n @Autowired\n private StateMachine stateMachine;\n\n\n @Bean\n public OrderStateService persist() {\n PersistStateMachineHandler handler = persistStateMachineHandler();\n handler.addPersistStateChangeListener(persistStateChangeListener());\n return new OrderStateService(handler);\n }\n\n @Bean\n public PersistStateMachineHandler persistStateMachineHandler() {\n return new PersistStateMachineHandler(stateMachine);\n }\n\n @Bean\n public OrderPersistStateChangeListener persistStateChangeListener(){\n return new OrderPersistStateChangeListener();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"訂單服務的Controller&Service示例","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"示例提供了兩個簡單的接口,一個是查看所有訂單列表,一個是改變一個訂單的狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Controller如下OrderController:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@RestController\n@RequestMapping(\"/orders\")\npublic class OrderController {\n\n @Autowired\n private OrderStateService orderStateService;\n\n /**\n * 列出所有的訂單列表\n *\n * @return\n */\n @RequestMapping(method = {RequestMethod.GET})\n public ResponseEntity orders() {\n String orders = orderStateService.listDbEntries();\n return new ResponseEntity(orders, HttpStatus.OK);\n\n }\n\n\n /**\n * 通過觸發一個事件,改變一個訂單的狀態\n * @param orderId\n * @param event\n * @return\n */\n @RequestMapping(value = \"/{orderId}\", method = {RequestMethod.POST})\n public ResponseEntity processOrderState(@PathVariable(\"orderId\") Integer orderId, @RequestParam(\"event\") OrderStatusChangeEvent event) {\n Boolean result = orderStateService.change(orderId, event);\n return new ResponseEntity(result, HttpStatus.OK);\n }\n\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"訂單服務類OrderStateService:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Component\npublic class OrderStateService {\n\n private PersistStateMachineHandler handler;\n\n\n public OrderStateService(PersistStateMachineHandler handler) {\n this.handler = handler;\n }\n\n @Autowired\n private OrderRepo repo;\n\n\n public String listDbEntries() {\n List orders = repo.findAll();\n StringJoiner sj = new StringJoiner(\",\");\n for (Order order : orders) {\n sj.add(order.toString());\n }\n return sj.toString();\n }\n\n\n public boolean change(int order, OrderStatusChangeEvent event) {\n Order o = repo.findByOrderId(order);\n return handler.handleEventWithState(MessageBuilder.withPayload(event).setHeader(\"order\", order).build(), o.getStatus());\n }\n\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章