
部署示例顯示了如何將狀態機概念與 UML 建模以提供通用錯誤處理狀態。此狀態 機器是一個相對復雜的例子,說明如何使用各種功能 提供集中式錯誤處理概念。 下圖顯示了部署狀態機:
前面的狀態圖是使用 Eclipse Papyrus 插件設計的 (參見Eclipse 建模支持)并通過生成的 UML 導入到 Spring StateMachine 中 模型文件。解析模型中定義的操作和防護 來自 Spring 應用程序上下文。 |
在此狀態機場景中,我們有兩種不同的行為 ( 和 ),該用戶嘗試執行。??DEPLOY?
???UNDEPLOY?
?
(資料圖)
在前面的狀態圖中:
在狀態中,輸入 和 狀態 條件。如果產品已經存在,我們將直接輸入 已安裝,如果安裝失敗,則無需嘗試。DEPLOY
INSTALL
START
START
START
在狀態中,如果應用程序是 已經在運行。UNDEPLOY
STOP
的條件選擇和通過 選擇這些狀態中的偽狀態,并選擇選項 由警衛。DEPLOY
UNDEPLOY
我們使用出口點偽狀態來更受控制地從 和 狀態退出。DEPLOY
UNDEPLOY
從和退出后,我們經過一個路口 偽狀態,用于選擇是否經歷狀態 (如果錯誤已添加到擴展狀態)。DEPLOY
UNDEPLOY
ERROR
最后,我們返回到狀態以處理新請求。READY
現在我們可以進入實際演示。運行基于啟動的示例應用程序 通過運行以下命令:
# java -jar spring-statemachine-samples-deploy-3.2.0.jar
在瀏覽器中,您可以看到如下圖所示的內容:
由于我們沒有真正的安裝、啟動或停止功能,因此我們 通過檢查特定消息頭是否存在來模擬故障。 |
現在,您可以開始向計算機發送事件并選擇各種 用于驅動功能的消息標頭。
訂單發貨示例顯示了如何使用狀態機概念 構建一個簡單的訂單處理系統。
下圖顯示了驅動此訂單發貨示例的狀態圖表。
在前面的狀態圖中:
狀態機進入(默認)狀態。WAIT_NEW_ORDER
事件轉換為狀態和條目 執行操作 ()。PLACE_ORDER
RECEIVE_ORDER
entryReceiveOrder
如果順序為 ,則狀態機進入兩個區域,一個處理順序 生產和一個處理用戶級支付。否則,狀態機將 成 ,這是最終狀態。OK
CUSTOMER_ERROR
狀態機在較低區域循環,提醒用戶付費 直到 發送成功以指示正確 付款。RECEIVE_PAYMENT
兩個區域都進入等待狀態 ( 和 ),它們在父正交狀態之前連接 () 已退出。WAIT_PRODUCT
WAIT_ORDER
HANDLE_ORDER
最后,狀態機進入其最終狀態 ().SHIP_ORDER
ORDER_SHIPPED
以下命令運行示例:
# java -jar spring-statemachine-samples-ordershipping-3.2.0.jar
在瀏覽器中,您可以看到類似于下圖的內容。您可以從選擇 客戶和訂單以創建狀態機。
現在已創建特定訂單的狀態機,您可以開始玩 下訂單和付款。其他設置(如 、 和 )允許您控制狀態 機器工作。 下圖顯示了等待訂單的狀態機:??makeProdPlan?
???produce?
???payment?
?
最后,您可以通過刷新頁面來查看計算機的功能,如下圖所示:
JPA 配置示例顯示了如何使用狀態機概念 將計算機配置保存在數據庫中。此示例使用 帶有 H2 控制臺的嵌入式 H2 數據庫(為了便于使用 數據庫)。
此示例使用(默認情況下, 自動配置 JPA 所需的存儲庫和實體類)。 因此,您只需要. 下面的示例顯示帶有批注的類:??spring-statemachine-autoconfigure?
???@SpringBootApplication?
???Application?
???@SpringBootApplication?
?
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
以下示例演示如何創建:??RepositoryStateMachineModelFactory?
?
@Configuration@EnableStateMachineFactorypublic static class Config extends StateMachineConfigurerAdapter{ @Autowired private StateRepository extends RepositoryState> stateRepository; @Autowired private TransitionRepository extends RepositoryTransition> transitionRepository; @Override public void configure(StateMachineModelConfigurer model) throws Exception { model .withModel() .factory(modelFactory()); } @Bean public StateMachineModelFactory modelFactory() { return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository); }}
可以使用以下命令運行示例:
# java -jar spring-statemachine-samples-datajpa-3.2.0.jar
訪問應用程序時,會彈出一個新的 為每個請求構建機器。然后,您可以選擇發送 事件到計算機。可能的事件和計算機配置為 使用每個請求從數據庫更新。 下圖顯示了 UI 和在以下情況下創建的初始事件 此狀態機啟動:??http://localhost:8080?
?
要訪問嵌入式控制臺,您可以使用 JDBC URL(如果它是 尚未設置)。 下圖顯示了 H2 控制臺:??jdbc:h2:mem:testdb?
?
在控制臺中,您可以查看數據庫表并修改 如你所愿。 下圖顯示了 UI 中簡單查詢的結果:
既然您已經走到了這一步,您可能想知道這些默認值是如何進行的 狀態和轉換已填充到數據庫中。春季數據 有一個很好的技巧來自動填充存儲庫,我們 通過 使用了此功能。 下面的示例展示了我們如何創建這樣的 bean:??Jackson2RepositoryPopulatorFactoryBean?
?
@Beanpublic StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")}); return factoryBean;}
以下清單顯示了我們用來填充數據庫的數據源:
[ { "@id": "10", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println("hello exit S1")" }, { "@id": "11", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println("hello entry S2")" }, { "@id": "12", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println("hello state S3")" }, { "@id": "13", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println("hello")" }, { "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1", "exitActions": ["10"] }, { "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2", "entryActions": ["11"] }, { "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S3", "stateActions": ["12"] }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", "source": "1", "target": "2", "event": "E1", "kind": "EXTERNAL" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", "source": "2", "target": "3", "event": "E2", "actions": ["13"] }]
數據持久化示例演示如何狀態計算機概念 使用外部存儲庫中的持久計算機。此示例使用 帶有 H2 控制臺的嵌入式 H2 數據庫(為了便于使用 數據庫)。或者,您也可以啟用 Redis 或 MongoDB。
此示例使用(默認情況下, 自動配置 JPA 所需的存儲庫和實體類)。 因此,您只需要. 下面的示例顯示帶有批注的類:??spring-statemachine-autoconfigure?
???@SpringBootApplication?
???Application?
???@SpringBootApplication?
?
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
該接口適用于運行時 A 的級別。它的實現 , 旨在與 JPA. 下面的清單創建一個 Bean:??StateMachineRuntimePersister?
???StateMachine?
???JpaPersistingStateMachineInterceptor?
???StateMachineRuntimePersister?
?
@Configuration@Profile("jpa")public static class JpaPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( JpaStateMachineRepository jpaStateMachineRepository) { return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
以下示例演示如何使用非常相似的配置 為 MongoDB 創建一個 bean:
@Configuration@Profile("mongo")public static class MongoPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( MongoDbStateMachineRepository jpaStateMachineRepository) { return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
以下示例演示如何使用非常相似的配置 為 Redis 創建一個 bean:
@Configuration@Profile("redis")public static class RedisPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( RedisStateMachineRepository jpaStateMachineRepository) { return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
可以使用配置方法配置為使用運行時持久性。 以下清單顯示了如何執行此操作:??StateMachine?
???withPersistence?
?
@Autowiredprivate StateMachineRuntimePersisterstateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister);}
此示例還使用 ,這使得 更易于與多臺機器配合使用。 下面的清單顯示了如何創建 的實例:??DefaultStateMachineService?
???DefaultStateMachineService?
?
@Beanpublic StateMachineServicestateMachineService( StateMachineFactory stateMachineFactory, StateMachineRuntimePersister stateMachineRuntimePersister) { return new DefaultStateMachineService (stateMachineFactory, stateMachineRuntimePersister);}
以下清單顯示了驅動此示例中的邏輯:??StateMachineService?
?
private synchronized StateMachinegetStateMachine(String machineId) throws Exception { listener.resetMessages(); if (currentStateMachine == null) { currentStateMachine = stateMachineService.acquireStateMachine(machineId); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) { stateMachineService.releaseStateMachine(currentStateMachine.getId()); currentStateMachine.stopReactively().block(); currentStateMachine = stateMachineService.acquireStateMachine(machineId); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } return currentStateMachine;}
可以使用以下命令運行示例:
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar
默認情況下,配置文件在 中處于啟用狀態。如果你想試試 其他后端,啟用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默認值, 但為了完整起見,我們將其包括在內):? # java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=jpa# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=mongo# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=redis |
在??http://localhost:8080??訪問應用程序會彈出一個新的 為每個請求構造狀態機,您可以選擇發送 事件到計算機??赡艿氖录陀嬎銠C配置為 使用每個請求從數據庫更新。
此示例中的狀態機具有狀態為“S1”的簡單配置 到“S6”,將事件“E1”轉換為“E6”,以在這些狀態機之間轉換狀態機 國家。您可以使用兩個狀態機標識符( 和 ) 來請求特定的狀態機。 下圖顯示了允許你選取計算機和事件的 UI,并顯示 當您執行以下操作時會發生什么:??datajpapersist1?
???datajpapersist2?
?
該示例默認使用計算機“datajpapersist1”并轉到其 初始狀態“S1”。 下圖顯示了使用這些默認值的結果:
如果發送事件并發送到狀態機,則其 狀態保留為“S3”。 下圖顯示了執行此操作的結果:??E1?
???E2?
???datajpapersist1?
?
如果隨后請求狀態機但不發送任何事件, 狀態機將恢復到其持久狀態 。??datajpapersist1?
???S3?
?
數據多樣本是另外兩個樣本的擴展:??JPA 配置??和數據??持久??。 我們仍然將機器配置保存在數據庫中,并保存到 數據庫。但是,這一次,我們還有一臺包含兩個正交的機器 區域,以顯示如何獨立地保留這些區域。此示例 還使用帶有 H2 控制臺的嵌入式 H2 數據庫(以方便播放 與數據庫)。
此示例使用(默認情況下, 自動配置 JPA 所需的存儲庫和實體類)。 因此,您只需要. 下面的示例顯示帶有批注的類:??spring-statemachine-autoconfigure?
???@SpringBootApplication?
???Application?
???@SpringBootApplication?
?
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
與其他數據驅動的樣本一樣,我們再次創建一個, 如以下清單所示:??StateMachineRuntimePersister?
?
@Beanpublic StateMachineRuntimePersisterstateMachineRuntimePersister( JpaStateMachineRepository jpaStateMachineRepository) { return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);}
豆子使使用機器變得更加容易。 下面的清單顯示了如何創建這樣的 Bean:??StateMachineService?
?
@Beanpublic StateMachineServicestateMachineService( StateMachineFactory stateMachineFactory, StateMachineRuntimePersister stateMachineRuntimePersister) { return new DefaultStateMachineService (stateMachineFactory, stateMachineRuntimePersister);}
我們使用 JSON 數據導入配置。 以下示例創建一個 Bean 來執行此操作:
@Beanpublic StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[] { new ClassPathResource("datajpamultipersist.json") }); return factoryBean;}
下面的清單顯示了我們如何獲得:??RepositoryStateMachineModelFactory?
?
@Configuration@EnableStateMachineFactorypublic static class Config extends StateMachineConfigurerAdapter{ @Autowired private StateRepository extends RepositoryState> stateRepository; @Autowired private TransitionRepository extends RepositoryTransition> transitionRepository; @Autowired private StateMachineRuntimePersister stateMachineRuntimePersister; @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister); } @Override public void configure(StateMachineModelConfigurer model) throws Exception { model .withModel() .factory(modelFactory()); } @Bean public StateMachineModelFactory modelFactory() { return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository); }}
可以使用以下命令運行示例:
# java -jar spring-statemachine-samples-datajpamultipersist-3.2.0.jar
訪問應用程序時,會彈出一個新的 為每個請求構建機器,并允許您發送 事件到計算機。可能的事件和狀態機配置為 針對每個請求從數據庫更新。我們還打印出來 所有狀態機上下文和當前根機, 如下圖所示:??http://localhost:8080?
?
命名的狀態機是一個簡單的“平面”機,其中狀態 、 和 分別由事件、 和 ()轉換。 但是,名為的狀態機包含兩個 根級別下直接的區域(和)。這就是為什么這個 根級計算機確實沒有狀態。我們需要 用于托管這些區域的根級別計算機。??datajpamultipersist1?
???S1?
???S2?
???S3?
???E1?
???E2?
???E3?
???datajpamultipersist2?
???R1?
???R2?
?
區域和狀態機分別包含狀態、、、和()。事件 , ,用于區域和事件 , 和事件用于區域 。下圖顯示了當我們 發送事件并發送到狀態機:??R1?
???R2?
???datajpamultipersist2?
???S10?
???S11?
???S12?
???S20?
???S21?
???S22?
???E10?
???E11?
???E12?
???R1?
???E20?
???E21?
???E22?
???R2?
???E10?
???E20?
???datajpamultipersist2?
?
區域有自己的上下文和自己的 ID,以及實際的 ID 后綴為 和區域 ID。如下圖所示, 數據庫中的不同區域具有不同的上下文:??#?
?
數據持久化示例演示如何狀態計算機概念 使用外部存儲庫中的持久計算機。此示例使用 帶有 H2 控制臺的嵌入式 H2 數據庫(為了便于使用 數據庫)?;蛘撸部梢詥⒂?Redis 或 MongoDB。
此示例使用(默認情況下, 自動配置 JPA 所需的存儲庫和實體類)。 因此,您只需要. 下面的示例顯示帶有批注的類:??spring-statemachine-autoconfigure?
???@SpringBootApplication?
???Application?
???@SpringBootApplication?
?
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
該接口適用于運行時 A 的級別。它的實現 , 旨在與 JPA. 下面的清單創建一個 Bean:??StateMachineRuntimePersister?
???StateMachine?
???JpaPersistingStateMachineInterceptor?
???StateMachineRuntimePersister?
?
@Configuration@Profile("jpa")public static class JpaPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( JpaStateMachineRepository jpaStateMachineRepository) { return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
以下示例演示如何使用非常相似的配置 為 MongoDB 創建一個 bean:
@Configuration@Profile("mongo")public static class MongoPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( MongoDbStateMachineRepository jpaStateMachineRepository) { return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
以下示例演示如何使用非常相似的配置 為 Redis 創建一個 bean:
@Configuration@Profile("redis")public static class RedisPersisterConfig { @Bean public StateMachineRuntimePersisterstateMachineRuntimePersister( RedisStateMachineRepository jpaStateMachineRepository) { return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository); }}
可以使用配置方法配置為使用運行時持久性。 以下清單顯示了如何執行此操作:??StateMachine?
???withPersistence?
?
@Autowiredprivate StateMachineRuntimePersisterstateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister);}
此示例還使用 ,這使得 更易于與多臺機器配合使用。 下面的清單顯示了如何創建 的實例:??DefaultStateMachineService?
???DefaultStateMachineService?
?
@Beanpublic StateMachineServicestateMachineService( StateMachineFactory stateMachineFactory, StateMachineRuntimePersister stateMachineRuntimePersister) { return new DefaultStateMachineService (stateMachineFactory, stateMachineRuntimePersister);}
以下清單顯示了驅動此示例中的邏輯:??StateMachineService?
?
private synchronized StateMachinegetStateMachine(String machineId) throws Exception { listener.resetMessages(); if (currentStateMachine == null) { currentStateMachine = stateMachineService.acquireStateMachine(machineId); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) { stateMachineService.releaseStateMachine(currentStateMachine.getId()); currentStateMachine.stopReactively().block(); currentStateMachine = stateMachineService.acquireStateMachine(machineId); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } return currentStateMachine;}
可以使用以下命令運行示例:
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar
默認情況下,配置文件在 中處于啟用狀態。如果你想試試 其他后端,啟用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默認值, 但為了完整起見,我們將其包括在內):? # java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=jpa# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=mongo# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=redis |
在??http://localhost:8080??訪問應用程序會彈出一個新的 為每個請求構造狀態機,您可以選擇發送 事件到計算機??赡艿氖录陀嬎銠C配置為 使用每個請求從數據庫更新。
此示例中的狀態機具有狀態為“S1”的簡單配置 到“S6”,將事件“E1”轉換為“E6”,以在這些狀態機之間轉換狀態機 國家。您可以使用兩個狀態機標識符( 和 ) 來請求特定的狀態機。 下圖顯示了允許你選取計算機和事件的 UI,并顯示 當您執行以下操作時會發生什么:??datajpapersist1?
???datajpapersist2?
?
該示例默認使用計算機“datajpapersist1”并轉到其 初始狀態“S1”。 下圖顯示了使用這些默認值的結果:
如果發送事件并發送到狀態機,則其 狀態保留為“S3”。 下圖顯示了執行此操作的結果:??E1?
???E2?
???datajpapersist1?
?
如果隨后請求狀態機但不發送任何事件, 狀態機將恢復到其持久狀態 。??datajpapersist1?
???S3?
?
監視示例演示如何使用狀態機概念來 監視狀態機轉換和操作。 下面的清單配置了我們用于此示例的狀態機:
@Configuration@EnableStateMachinepublic static class Config extends StateMachineConfigurerAdapter{ @Override public void configure(StateMachineStateConfigurer states) throws Exception { states .withStates() .initial("S1") .state("S2", null, (c) -> {System.out.println("hello");}) .state("S3", (c) -> {System.out.println("hello");}, null); } @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions .withExternal() .source("S1").target("S2").event("E1") .action((c) -> {System.out.println("hello");}) .and() .withExternal() .source("S2").target("S3").event("E2"); }}
可以使用以下命令運行示例:
# java -jar spring-statemachine-samples-monitoring-3.2.0.jar
下圖顯示了狀態機的初始狀態:
下圖顯示了狀態機的狀態,在我們有 執行了一些操作:
您可以通過運行以下兩個命令(與其輸出一起顯示)從 Spring 引導中查看指標:??curl?
?
# curl http://localhost:8080/actuator/metrics/ssm.transition.duration{ "name":"ssm.transition.duration", "measurements":[ { "statistic":"COUNT", "value":3.0 }, { "statistic":"TOTAL_TIME", "value":0.007 }, { "statistic":"MAX", "value":0.004 } ], "availableTags":[ { "tag":"transitionName", "values":[ "INITIAL_S1", "EXTERNAL_S1_S2" ] } ]}
# curl http://localhost:8080/actuator/metrics/ssm.transition.transit{ "name":"ssm.transition.transit", "measurements":[ { "statistic":"COUNT", "value":3.0 } ], "availableTags":[ { "tag":"transitionName", "values":[ "EXTERNAL_S1_S2", "INITIAL_S1" ] } ]}
您還可以通過運行以下命令(與其輸出一起顯示)來查看來自 Spring 引導的跟蹤:??curl?
?
# curl http://localhost:8080/actuator/statemachinetrace[ { "timestamp":"2018-02-11T06:44:12.723+0000", "info":{ "duration":2, "machine":null, "transition":"EXTERNAL_S1_S2" } }, { "timestamp":"2018-02-11T06:44:12.720+0000", "info":{ "duration":0, "machine":null, "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f" } }, { "timestamp":"2018-02-11T06:44:12.714+0000", "info":{ "duration":1, "machine":null, "transition":"INITIAL_S1" } }, { "timestamp":"2018-02-11T06:44:09.689+0000", "info":{ "duration":4, "machine":null, "transition":"INITIAL_S1" } }]
本章回答了Spring Statemachine用戶最常問的問題。
如何自動過渡到下一個狀態?
您可以從三種方法中進行選擇:
實現操作并將適當的事件發送到狀態機 以觸發轉換到正確的目標狀態。在狀態中定義延遲事件,在發送事件之前, 發送另一個延遲的事件。這樣做會導致下一個 處理起來更方便時適當的狀態轉換 那個事件。實現無觸發轉換,這會自動導致 進入狀態時的狀態轉換到下一個狀態及其 操作已完成。如何在狀態機啟動時初始化變量?
狀態機中的一個重要概念是,什么都沒有真正發生 除非觸發器導致狀態轉換 然后可以觸發動作。然而,話雖如此,彈簧狀態機 啟動狀態機時始終具有初始轉換。跟 在此初始轉換中,您可以運行一個簡單的操作,該操作在 a ,可以對擴展狀態做任何它喜歡的事情 變量。??StateContext?
?
本附錄提供有關類和 本參考文檔中使用的材料。
以下清單顯示了本參考指南中使用的類:
public enum States { SI,S1,S2,S3,S4,SF}
public enum States2 { S1,S2,S3,S4,S5,SF, S2I,S21,S22,S2F, S3I,S31,S32,S3F}
public enum States3 { S1,S2,SH, S2I,S21,S22,S2F}
public enum Events { E1,E2,E3,E4,EF}
本附錄提供有關狀態機的通用信息。
假設我們有命名的狀態和命名的事件和 ,您可以定義狀態機的邏輯,如下圖所示:??STATE1?
???STATE2?
???EVENT1?
???EVENT2?
?
以下清單定義了上圖中的狀態機:
public enum States { STATE1, STATE2}public enum Events { EVENT1, EVENT2}
@Configuration@EnableStateMachinepublic class Config1 extends EnumStateMachineConfigurerAdapter{ @Override public void configure(StateMachineStateConfigurer states) throws Exception { states .withStates() .initial(States.STATE1) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions .withExternal() .source(States.STATE1).target(States.STATE2) .event(Events.EVENT1) .and() .withExternal() .source(States.STATE2).target(States.STATE1) .event(Events.EVENT2); }}
@WithStateMachinepublic class MyBean { @OnTransition(target = "STATE1") void toState1() { } @OnTransition(target = "STATE2") void toState2() { }}
public class MyApp { @Autowired StateMachinestateMachine; void doSignals() { stateMachine .sendEvent(Mono.just(MessageBuilder .withPayload(Events.EVENT1).build())) .subscribe(); stateMachine .sendEvent(Mono.just(MessageBuilder .withPayload(Events.EVENT2).build())) .subscribe(); }}
狀態機
驅動狀態集合的主要實體,以及區域, 轉換和事件。
州
狀態模擬了某種不變條件的情況 持有。狀態是狀態機的主要實體,其中狀態更改 由事件驅動。
擴展狀態
擴展狀態是保存在狀態中的一組特殊變量 機器來減少所需狀態的數量。
過渡
轉換是源狀態和目標之間的關系 州。它可能是復合轉換的一部分,它采用狀態 機器從一種狀態配置到另一種狀態配置,代表完整的 狀態機對發生 特定類型。
事件
發送到狀態機然后驅動各種 狀態更改。
初始狀態
狀態機啟動的特殊狀態。初始狀態為 始終綁定到特定的狀態機或區域。一個狀態 具有多個區域的計算機可能具有多個初始狀態。
結束狀態
(也稱為最終狀態。一種特殊的狀態,表示 封閉區域已完成。如果封閉區域是 直接包含在狀態機和所有其他區域中 狀態機也都完成了,整個狀態 機器完成。
歷史狀態
一種偽狀態,讓狀態機記住其上一個狀態 活動狀態。存在兩種類型的歷史記錄狀態:淺(僅記住頂級狀態)和深層(記住 子機器)。
選擇狀態
一種偽狀態,允許根據(例如)做出轉換選擇 事件標頭或擴展狀態變量。
結狀態
與選擇狀態相對相似但允許的偽狀態 多個傳入轉換,而選擇只允許一個傳入 過渡。
分叉狀態
提供對區域的受控進入的偽狀態。
加入狀態
從區域提供受控退出的偽狀態。
入口點
允許受控進入子計算機的偽狀態。
出口點
允許從子計算機受控退出的偽狀態。
地區
區域是復合狀態或狀態的正交部分 機器。它包含狀態和轉換。
警衛
根據 擴展狀態變量和事件參數。防護條件影響 通過啟用操作或轉換的狀態機行為 僅當他們評估時,當他們評估時禁用他們 自。??TRUE?
???FALSE?
?
行動
操作是在觸發 過渡。
本附錄提供了狀態機的通用速成課程 概念。
狀態是狀態機可以存在的模型。它總是 更容易將狀態描述為現實世界的示例,而不是試圖使用 抽象概念 通用文檔。為此,請考慮 一個簡單的鍵盤示例 - 我們大多數人每天都使用一個。 如果您有一個完整的鍵盤,左側有普通鍵,并且 右側的數字鍵盤,您可能已經注意到 數字鍵盤可能處于兩種不同的狀態,具體取決于 數字鎖定被激活。如果它未處于活動狀態,請按數字鍵盤鍵 導致使用箭頭等進行導航。如果數字鍵盤處于活動狀態,請按 這些鍵會導致鍵入數字。本質上,鍵盤的數字鍵盤部分 可以處于兩種不同的狀態。
將狀態概念與編程聯系起來,這意味著而不是使用 標志、嵌套的 if/else/break 子句或其他不切實際(有時是曲折)的邏輯,您可以 依賴于狀態、狀態變量或與 狀態機。
偽狀態是一種特殊類型的狀態,通常會引入更多 通過為狀態提供 A 來將更高級別的邏輯放入狀態機 特殊含義(如初始狀態)。然后,狀態機可以在內部 通過執行在 UML 狀態下可用的各種操作來響應這些狀態 機器概念。
每個狀態始終需要初始偽狀態機器,無論您有簡單的一級狀態機還是更多 由子機器或區域組成的復雜狀態機。初始 狀態定義狀態機在啟動時應去哪里。 沒有它,狀態機的格式不正確。
終止偽狀態(也稱為“結束狀態”)指示 特定狀態機已達到其最終狀態。有效 這意味著狀態機不再處理任何事件,并且 不過境到任何其他州。但是,在子機器 區域,狀態機可以從其終端狀態重新啟動。
您可以使用選擇偽狀態選擇動態條件分支 從此狀態過渡。動態條件由防護裝置評估 以便選擇一個分支。通常 簡單的 if/elseif/else 結構用于確保一個 分支處于選中狀態。否則,狀態機最終可能會陷入死鎖, 并且配置格式不正確。
交匯點偽狀態在功能上類似于選擇,因為兩者都是 使用 if/elseif/else 結構實現。唯一真正的區別是 該交匯點允許多個傳入轉換,而選擇 只允許一個。因此,差異主要是學術上的,但確實有一些 差異,例如何時將狀態機設計與實際 UI 建模一起使用 框架。
您可以使用歷史記錄偽狀態來記住上一個活動狀態 配置。狀態機退出后,可以使用歷史記錄狀態 以恢復以前已知的配置。有兩種類型 可用的歷史記錄狀態數:(僅記住 狀態機本身)和(也記住嵌套狀態)。??SHALLOW?
???DEEP?
?
歷史狀態可以通過偵聽狀態在外部實現 機器事件,但這很快就會使邏輯變得非常困難, 特別是當狀態機包含復雜的嵌套結構時。 讓狀態機本身處理歷史狀態的記錄 讓事情變得簡單得多。用戶只需創建一個 轉換到歷史狀態,狀態機處理所需的 邏輯返回到其上一個已知的記錄狀態。
如果轉換在歷史狀態終止,則當狀態 以前沒有輸入過(換句話說,沒有先前的歷史記錄存在)或已達到其 結束狀態,轉換可以強制狀態機到特定的子狀態,通過 使用默認歷史記錄機制。這種轉變源于 處于歷史記錄狀態,并終止于特定頂點(默認歷史記錄 狀態)包含歷史記錄狀態的區域。這種轉變是 只有當它的執行導致歷史狀態并且狀態以前從未有過時,才會采取 積極。否則,將執行進入該區域的正常歷史記錄條目。 如果未定義默認歷史記錄轉換,則標準默認條目 執行區域。
您可以使用Fork 偽狀態顯式進入一個或多個區域。 下圖顯示了分叉的工作原理:
目標狀態可以是托管區域的父狀態,只需 表示通過輸入初始狀態來激活區域。你 還可以將目標直接添加到區域中的任何狀態,從而 允許更受控地進入狀態。
聯接偽狀態將幾個轉換合并在一起, 來自不同的地區。它通常用于等待 并阻止參與區域進入其加入目標狀態。 下圖顯示了聯接的工作原理:
源狀態可以是托管區域的父狀態,這意味著 加入狀態是參與區域的終端狀態。 您還可以將源狀態定義為 區域,允許從區域受控退出。
入口點偽狀態表示狀態的入口點機器或提供內部封裝的復合狀態 的狀態或狀態機。在狀態機的每個區域中或 擁有入口點的復合狀態,最多只有一個 從入口點過渡到該區域內的頂點。
退出點偽狀態是狀態機或 復合狀態,提供狀態內部的封裝 或狀態機。在任何退出點終止的過渡 復合狀態的區域(或由 子機器狀態)意味著退出此復合狀態或子機器 狀態(執行其關聯的退出行為)。
保護條件是基于擴展狀態變量和事件參數計算結果為 或 的表達式。警衛 與動作和過渡一起使用,以動態選擇是否 應運行特定操作或轉換。守衛的各種奇觀, 存在事件參數和擴展狀態變量以創建狀態 機器設計簡單得多。??TRUE?
???FALSE?
?
事件是驅動狀態機最常用的觸發器行為。 還有其他方法可以在狀態機中觸發行為 (例如計時器),但事件才是真正讓用戶 與狀態機交互。事件也稱為“信號”。 它們基本上表示可能改變狀態機狀態的東西。
轉換是源狀態和目標之間的關系 州。從一種狀態切換到另一種狀態是導致的狀態轉換 通過觸發器。
當操作需要在沒有的情況下運行時使用內部轉換 導致狀態轉換。在內部轉換中,源狀態和目標 狀態總是相同的,它與 沒有國家進入和退出操作。
在大多數情況下,外部和局部轉換在功能上是 等效,除非在超級之間發生轉換 和子狀態。本地轉換不會導致退出和進入 源狀態(如果目標狀態是源狀態的子狀態)。 相反,本地轉換不會導致退出和進入目標 狀態,如果目標是源狀態的超狀態。 下圖顯示了本地和外部轉換之間的差異 使用非常簡單的超級和子狀態:
觸發器開始過渡。觸發器可以由事件或計時器驅動。
動作真正粘合狀態機狀態變化 到用戶自己的代碼。狀態機可以對各種 狀態機中的更改和步驟(例如進入或退出狀態) 或進行狀態轉換。
操作通??梢栽L問狀態上下文,這提供了運行 對選項進行編碼,以各種方式與狀態機進行交互。 狀態上下文公開整個狀態機,以便用戶可以 訪問擴展狀態變量、事件標頭(如果轉換基于 在事件上)或實際過渡(可以看到更多 詳細說明此狀態更改的來源和去向)。
分層狀態機的概念用于簡化狀態 當特定狀態必須同時存在時進行設計。
分層狀態實際上是UML狀態機的一項創新 傳統的狀態機,如Mealy或Moore機。 分層狀態允許您定義某種級別的抽象(并行 Java 開發人員如何使用抽象定義類結構 類)。例如,使用嵌套狀態機,您可以 在多個狀態級別上定義轉換(可能使用 不同的條件)。狀態機總是試圖查看當前 狀態能夠與轉換保護一起處理事件 條件。如果這些條件的計算結果不為 ,則狀態 機器只是看到超級狀態可以處理什么。??TRUE?
?
通常查看區域(也稱為正交區域) 作為應用于狀態的獨占 OR (XOR) 操作。區域中區域的概念 狀態機的術語通常有點難以理解, 但是通過一個簡單的例子,事情變得簡單一些。
我們中的一些人有一個全尺寸鍵盤,主鍵在左側,數字 鍵在右側。你可能已經注意到,雙方真的 有自己的狀態,如果你按“數字鎖”鍵( 僅更改數字鍵盤本身的行為)。如果您沒有全尺寸 鍵盤,您可以購買外部USB數字鍵盤。 鑒于鍵盤的左側和右側都可以在沒有 其他,它們必須具有完全不同的狀態,這意味著它們是 在不同的狀態機上運行。在狀態機術語中,主要部分 鍵盤是一個區域,數字鍵盤是另一個區域。
處理兩個不同的會有點不方便 狀態機作為完全獨立的實體,因為它們 仍然以某種方式一起工作。這種獨立性讓正交區域 在單個狀態內以多個同時狀態組合在一起 在狀態機中。
本附錄提供了有關以下內容的更詳細的技術文檔。 將 Zookeeper 實例與 Spring Statemachine 一起使用。
在單個狀態機之上引入“分布式狀態” 在單個 JVM 上運行的實例是一個困難而復雜的主題。 “分布式狀態機”的概念引入了一些相對復雜的 簡單狀態機頂部的問題,由于其運行到完成 模型,更一般地說,由于其單線程執行模型, 盡管正交區域可以并行運行。另一種天然 問題是狀態機轉換執行是由觸發器驅動的, 要么基于。??event?
???timer?
?
彈簧狀態機嘗試解決跨越問題 通過JVM邊界的通用“狀態機”,支持分布式 狀態機。在這里,我們展示了您可以使用通用 跨多個JVM和Spring的“狀態機”概念 應用程序上下文。
我們發現,如果仔細選擇抽象 支持分布式狀態存儲庫保證了就緒性,它是 可以創建可以共享的一致狀態機 在集合中的其他狀態機之間分布式狀態。??Distributed State Machine?
???CP?
?
我們的結果表明,如果支持,分布式狀態變化是一致的 存儲庫是“CP”(稍后討論)。 我們預計我們的分布式狀態機可以提供 需要與共享分布式應用程序合作的基礎 國家。該模型旨在為云應用程序提供良好的方法 有更簡單的相互交流方式,而無需 顯式構建這些分布式狀態概念。
Spring 狀態機不強制使用單線程執行 模型,因為一旦使用了多個區域,就可以在 如果應用了必要的配置,則并行。這是一個重要的 主題,因為,一旦用戶想要擁有并行狀態機 執行,它使獨立區域的狀態更改速度更快。
當狀態更改不再由本地 JVM 中的觸發器驅動時,或者 本地狀態機實例,需要控制轉換邏輯 外部在任意持久存儲中。此存儲需要 有辦法在分布式時通知參與狀態機 狀態已更改。
CAP定理指出 分布式計算機系統不可能同時 提供以下所有三個保證:一致性, 可用性和分區容錯。
這意味著, 無論為后備持久性存儲選擇什么,都建議 成為“CP”。在這種情況下,“CP”的意思是“一致性”和“分區” 寬容”。自然,分布式彈簧狀態機不在乎 關于其“CAP”水平,但實際上,“一致性”和 “分區容錯”比“可用性”更重要。這是 (例如)Zookeeper使用“CP”存儲的確切原因。
本文中介紹的所有測試都是通過運行自定義完成的 杰普森在以下環境中進行測試:
具有節點 n1、n2、n3、n4 和 n5 的集群。每個節點都有一個實例,該實例構造一個融合 所有其他節點。Zookeeper
每個節點都安裝了一個Web示例, 以連接到本地節點。Zookeeper
每個狀態機實例僅與本地實例通信。將一臺計算機連接到多個實例時 是可能的,這里不使用。Zookeeper
所有狀態機實例在啟動時,都會使用 Zookeeper 集合體創建 。StateMachineEnsemble
每個示例都包含一個自定義 rest API,Jepsen 使用該 API 發送該 API 事件并檢查特定狀態機狀態。所有杰普森測試均可從杰普森獲得 測試。??Spring Distributed Statemachine?
?
一個設計決策不是使每個 單個狀態機實例請注意它是 “分布式合奏”。因為可以通過其界面訪問其主要功能和特性,所以 將此實例包裝在 中,其中 攔截所有狀態機通信并與 集成以編排分布式狀態更改。??Distributed State Machine?
???StateMachine?
???DistributedStateMachine?
?
另一個重要的概念是能夠堅持足夠多 來自狀態機的信息,用于重置狀態機狀態 從任意狀態轉變為新的反序列化狀態。這是自然的 當新的狀態機實例與整體聯接時需要 并且需要將自己的內部狀態與分布式同步 州。以及使用分布式狀態和狀態的概念 持久化,可以創建分布式狀態機。 目前,唯一的后備存儲庫是 通過使用 Zookeeper 實現。??Distributed State Machine?
?
如使用分布式狀態中所述,分布式狀態由 將 的實例包裝在 中。具體實現提供 與Zookeeper集成。??StateMachine?
???DistributedStateMachine?
???StateMachineEnsemble?
???ZookeeperStateMachineEnsemble?
?
?ZookeeperStateMachinePersist?
?我們希望有一個通用接口 () 可以持久化到任意存儲中,并為 實現此接口。??StateMachinePersist?
???StateMachineContext?
???ZookeeperStateMachinePersist?
???Zookeeper?
?
?ZookeeperStateMachineEnsemble?
?而分布式狀態機使用一組序列化的 上下文來更新自己的狀態,使用 ZooKeeper,我們有一個 關于如何傾聽這些上下文變化的概念問題。我們 可以將上下文序列化為動物園管理員并最終 偵聽數據何時被修改。但是,沒有 保證您收到每次數據更改的通知, 因為 A 的注冊一旦觸發就會被禁用 并且用戶需要重新注冊該.在這短短的時間里, 數據可以更改,從而導致丟失事件。是的 實際上很容易通過更改數據來錯過這些事件 多個線程并發方式。??znode?
???znode?
???Zookeeper?
???watcher?
???znode?
???watcher?
???znode?
?
為了克服這個問題,我們保留了單獨的上下文更改 在多個中,我們使用一個簡單的整數計數器來標記 這是當前活動的一個。這樣做讓我們重播錯過的 事件。我們不想創建越來越多的znode,然后再創建 刪除舊的。相反,我們使用循環的簡單概念 Znode 集。這允許我們使用一組預定義的 znode,其中 可以使用簡單的整數計數器確定當前節點。我們已經有了 此計數器通過跟蹤主數據版本(其中,在 中是一個整數)。??znodes?
???znode?
???znode?
???Zookeeper?
?
循環緩沖區的大小強制為 2 的冪,以避免 整數溢出時出現問題。因此,我們不需要 處理任何特定情況。
顯示各種分布式操作如何針對狀態 現實生活中的機器工作,我們使用一組杰普森測試來 模擬真實分布式中可能發生的各種條件 簇。這些包括網絡級別的“大腦分裂”,并行 具有多個“分布式狀態機”的事件,以及 “擴展狀態變量”。Jepsen 測試基于示例Web,其中此示例實例運行在 多個主機以及每個節點上的 Zookeeper 實例 運行狀態機的位置。本質上,每個狀態機示例 連接到本地 Zookeeper 實例,該實例允許我們使用 杰普森,模擬網絡狀況。
本章后面顯示的繪制圖形包含以下狀態和事件: 直接映射到狀態圖,您可以在Web中找到該狀態圖。
將隔離的單個事件發送到一個狀態機中 集成是最簡單的測試場景,它演示了 一個狀態機中的狀態更改被正確地傳播到另一個狀態機 集合中的狀態機。
在此測試中,我們演示了一臺機器中的狀態更改 最終導致其他計算機的狀態發生一致的更改。 下圖顯示了測試狀態機的事件和狀態更改:
在上圖中:
所有計算機都報告狀態 。S21
事件發送到節點,所有節點報告狀態更改 從 到 。I
n1
S21
S22
事件發送到節點,所有節點報告狀態更改 從 到 。C
n2
S22
S211
事件發送到節點,所有節點報告狀態更改 從 到 。I
n5
S211
S212
事件發送到節點,所有節點報告狀態更改 從 到 。K
n3
S212
S21
我們通過隨機節點循環事件、、、和一次。I
C
I
K
多個分布式狀態機的一個邏輯問題是,如果 同一事件以完全相同的方式發送到多個狀態機 時間,則只有一個事件會導致分布式狀態 轉換。這是一個有點意料之中的場景,因為第一個狀態 能夠更改分布式狀態的計算機(對于此事件) 控制分布式轉換邏輯。實際上,所有其他 接收此相同事件的計算機以靜默方式丟棄該事件, 因為分布式狀態不再處于特定狀態 可以處理事件。
在下圖所示的測試中,我們演示了由 整個融合中的并行事件最終會導致 所有計算機的狀態更改一致:
在上圖中,我們使用與上一示例中使用的相同事件流 (孤立事件),不同之處在于事件始終是 發送到所有節點。
擴展狀態機變量不保證在 任何給定時間,但是,在分布式狀態更改后,所有狀態機 在融合中應具有同步擴展狀態。
在此測試中,我們證明了擴展狀態的變化 一個分布式狀態機中的變量最終變成 在所有分布式狀態機中保持一致。 下圖顯示了此測試:
在上圖中:
事件被發送到事件變量具有值的節點。然后,所有節點都報告具有一個值為 .J
n5
testVariable
v1
testVariable
v1
事件從變量重復到 ,執行相同的檢查。J
v2
v8
我們需要始終假設,遲早,集群中的事物 變壞了,無論是 Zookeeper 實例的崩潰,還是狀態 機器崩潰,或網絡問題,如“大腦分裂”。(腦裂是 現有集群成員被隔離的情況,以便只有 部分主機能夠看到彼此)。通常的情況是 大腦分裂造成少數和多數分裂 合奏,使得少數人的主持人不能參與合奏 直到網絡狀態已恢復。
在下面的測試中,我們證明了各種類型的大腦分裂 融合最終會導致所有 分布式狀態機。
有兩種情況的大腦直接分裂在一個 位置和實例所在的網絡 分成兩半(假設每個連接到 本地實例):??Zookeeper?
???Statemachine?
???Statemachine?
???Zookeeper?
?
在我們目前的Jepsen測試中,我們無法分離Zookeeper裂腦 領導者被留在多數或少數之間的場景,所以我們需要 多次運行測試以完成此情況。 |
在下面的圖中,我們將狀態機錯誤狀態映射到 ,以指示狀態機處于錯誤狀態,而不是 正常狀態。在解釋圖表狀態時,請記住這一點。? |
在第一個測試中,我們展示了當現有的動物園管理員領導者 保持大多數,五分之三的機器繼續保持原樣。 下圖顯示了此測試:
在上圖中:
第一個事件 將發送到所有計算機,從而導致狀態更改為 。C
S211
杰普森克星導致大腦分裂,從而導致分裂 的和。節點留在少數,并且 節點構建新的健康多數。節點中的 多數人保持正常運行,但少數人中的節點 進入錯誤狀態。n1/n2/n5
n3/n4
n3/n4
n1/n2/n5
Jepsen修復了網絡,一段時間后,節點加入 返回到融合并同步其分布式狀態。n3/n4
最后,將事件發送到所有狀態機,以確保 工作正常。此狀態更改導致返回到狀態 。K1
S21
在第二個測試中,我們表明,當現有的動物園管理員領導者是 保持在少數,所有機器都出錯了。 下圖顯示了第二個測試:
在上圖中:
第一個事件 將發送到導致狀態更改為 的所有計算機。C
S211
杰普森克星導致大腦分裂,從而導致分裂 這樣現有的領導人就保持在少數和所有人中 實例與整體斷開連接。Zookeeper
Jepsen修復了網絡,一段時間后,所有節點都加入了 返回到融合并同步其分布式狀態。最后,將事件發送到所有狀態機,以確保 正常工作。此狀態更改導致返回到狀態 。K1
S21
在這個測試中,我們證明了殺死現有的狀態機 然后將新實例重新加入到融合中,使 分布式狀態正常,新加入的狀態機同步 他們的狀態正確。 下圖顯示了崩潰和聯接容差測試:
在此測試中,不會在第一個 和 最后一個之間檢查狀態。 因此,該圖顯示了兩者之間的一條平線。檢查狀態 狀態更改在 和 之間發生的確切位置。? |
在上圖中:
所有狀態機都從初始狀態 () 轉換為 狀態,以便我們可以在連接期間測試正確的狀態同步。S21
S211
??X?
?標記特定節點何時崩潰并啟動。同時,我們請求所有機器的狀態并繪制結果。最后,我們做一個簡單的過渡回到 from 以 確保所有狀態機仍正常運行。S21
S211
本附錄為開發人員提供了一般信息,他們可能 想要貢獻或其他想要了解狀態的人 機器工作或理解其內部概念。
??StateMachineModel?
?和其他相關的SPI類是一個抽象 在各種配置和工廠類之間。這也允許 其他人更容易集成以構建狀態機。
如下面的清單所示,您可以通過構建模型來實例化狀態機 使用配置數據類,然后要求工廠構建一個 狀態機:
// setup configuration dataConfigurationDataconfigurationData = new ConfigurationData<>();// setup states dataCollection > stateData = new ArrayList<>();stateData.add(new StateData ("S1", true));stateData.add(new StateData ("S2"));StatesData statesData = new StatesData<>(stateData);// setup transitions dataCollection > transitionData = new ArrayList<>();transitionData.add(new TransitionData ("S1", "S2", "E1"));TransitionsData transitionsData = new TransitionsData<>(transitionData);// setup modelStateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);// instantiate machine via factoryObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(stateMachineModel);StateMachine stateMachine = factory.getStateMachine();
工作的主要任務是內部和外部移動和改變 我們盡可能多地從命令式代碼進入一個響應式世界。這意味著一些 的主接口添加了一個新的 reative 方法和大部分內部執行位點 (如適用)已移至反應堆處理。從本質上講,這意味著線程處理模型與 有很大不同。以下章節 經歷所有這些變化。??3.x?
???2.x?
?
我們添加了新的反應式方法,同時仍保留舊的阻塞事件 方法到位。??StateMachine?
?
Flux> sendEvent(Mono > event);Flux > sendEvents(Flux > events);Mono >> sendEventCollect(Mono
> event);
我們現在只研究彈簧、反應堆和類。 您可以發送 a 的 并接收回 的 。 請記住,在您訂閱此之前不會發生任何事情。更多關于 此返回值,請參閱狀態機事件結果。方法只是一個語法糖,傳入 a 并得到一個包裝 結果以列表形式出現。??Message?
???Mono?
???Flux?
???Mono?
???Message?
???Flux?
???StateMachineEventResult?
???Flux?
???sendEventCollect?
???Mono?
???Mono?
?
Messagemessage = MessageBuilder.withPayload("EVENT").build();machine.sendEvent(Mono.just(message)).subscribe();
您還可以發送一條消息,而不是一條消息。??Flux?
???Mono?
?
machine.sendEvents(Flux.just(message)).subscribe();
所有反應器方法都由您處置,例如不要阻塞和 在事件處理完成后執行某些操作,您可以執行類似操作。
Mono> mono = Mono.just(MessageBuilder.withPayload("EVENT").build());machine.sendEvent(mono) .doOnComplete(() -> { System.out.println("Event handling complete"); }) .subscribe();
返回 for 接受狀態的舊 API 方法仍然存在 但已棄用,以便在將來的版本中被刪除。??boolean?
?
boolean accepted = machine.sendEvent("EVENT");
狀態機執行和狀態操作調度已被完全取代,有利于或反應器執行和調度。??TaskExecutor?
???TaskScheduler?
?
基本上,在兩個地方需要在主線程之外執行,首先是需要可取消的狀態操作,其次是應該?始終獨立執行。目前,我們選擇只使用Reactor來做這些應該會給出相對好的結果,因為它 嘗試自動使用系統中可用數量的 CPU 內核。??Schedulers.parallel()?
?
雖然大多數示例仍然相同,但我們已經徹底修改了其中的一些示例,并且 創建了一些新的:
調諧器反應性旋轉門反應式旋轉門反應