天天快看:Spring Statemachine狀態機的概念(五)

2022-12-20 14:11:16 來源:51CTO博客

部署

部署示例顯示了如何將狀態機概念與 UML 建模以提供通用錯誤處理狀態。此狀態 機器是一個相對復雜的例子,說明如何使用各種功能 提供集中式錯誤處理概念。 下圖顯示了部署狀態機:

前面的狀態圖是使用 Eclipse Papyrus 插件設計的 (參見Eclipse 建模支持)并通過生成的 UML 導入到 Spring StateMachine 中 模型文件。解析模型中定義的操作和防護 來自 Spring 應用程序上下文。

在此狀態機場景中,我們有兩種不同的行為 ( 和 ),該用戶嘗試執行。??DEPLOY????UNDEPLOY??


(資料圖)

在前面的狀態圖中:

在狀態中,輸入 和 狀態 條件。如果產品已經存在,我們將直接輸入 已安裝,如果安裝失敗,則無需嘗試。DEPLOYINSTALLSTARTSTARTSTART在狀態中,如果應用程序是 已經在運行。UNDEPLOYSTOP的條件選擇和通過 選擇這些狀態中的偽狀態,并選擇選項 由警衛。DEPLOYUNDEPLOY我們使用出口點偽狀態來更受控制地從 和 狀態退出。DEPLOYUNDEPLOY從和退出后,我們經過一個路口 偽狀態,用于選擇是否經歷狀態 (如果錯誤已添加到擴展狀態)。DEPLOYUNDEPLOYERROR最后,我們返回到狀態以處理新請求。READY

現在我們可以進入實際演示。運行基于啟動的示例應用程序 通過運行以下命令:

# java -jar spring-statemachine-samples-deploy-3.2.0.jar

在瀏覽器中,您可以看到如下圖所示的內容:

由于我們沒有真正的安裝、啟動或停止功能,因此我們 通過檢查特定消息頭是否存在來模擬故障。

現在,您可以開始向計算機發送事件并選擇各種 用于驅動功能的消息標頭。

訂單運輸

訂單發貨示例顯示了如何使用狀態機概念 構建一個簡單的訂單處理系統。

下圖顯示了驅動此訂單發貨示例的狀態圖表。

在前面的狀態圖中:

狀態機進入(默認)狀態。WAIT_NEW_ORDER事件轉換為狀態和條目 執行操作 ()。PLACE_ORDERRECEIVE_ORDERentryReceiveOrder如果順序為 ,則狀態機進入兩個區域,一個處理順序 生產和一個處理用戶級支付。否則,狀態機將 成 ,這是最終狀態。OKCUSTOMER_ERROR狀態機在較低區域循環,提醒用戶付費 直到 發送成功以指示正確 付款。RECEIVE_PAYMENT兩個區域都進入等待狀態 ( 和 ),它們在父正交狀態之前連接 () 已退出。WAIT_PRODUCTWAIT_ORDERHANDLE_ORDER最后,狀態機進入其最終狀態 ().SHIP_ORDERORDER_SHIPPED

以下命令運行示例:

# java -jar spring-statemachine-samples-ordershipping-3.2.0.jar

在瀏覽器中,您可以看到類似于下圖的內容。您可以從選擇 客戶和訂單以創建狀態機。

現在已創建特定訂單的狀態機,您可以開始玩 下訂單和付款。其他設置(如 、 和 )允許您控制狀態 機器工作。 下圖顯示了等待訂單的狀態機:??makeProdPlan????produce????payment??

最后,您可以通過刷新頁面來查看計算機的功能,如下圖所示:

JPA 配置

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 stateRepository;  @Autowired  private TransitionRepository 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 StateMachineRuntimePersister stateMachineRuntimePersister(      JpaStateMachineRepository jpaStateMachineRepository) {    return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

以下示例演示如何使用非常相似的配置 為 MongoDB 創建一個 bean:

@Configuration@Profile("mongo")public static class MongoPersisterConfig {  @Bean  public StateMachineRuntimePersister stateMachineRuntimePersister(      MongoDbStateMachineRepository jpaStateMachineRepository) {    return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

以下示例演示如何使用非常相似的配置 為 Redis 創建一個 bean:

@Configuration@Profile("redis")public static class RedisPersisterConfig {  @Bean  public StateMachineRuntimePersister stateMachineRuntimePersister(      RedisStateMachineRepository jpaStateMachineRepository) {    return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

可以使用配置方法配置為使用運行時持久性。 以下清單顯示了如何執行此操作:??StateMachine????withPersistence??

@Autowiredprivate StateMachineRuntimePersister stateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer config)    throws Exception {  config    .withPersistence()      .runtimePersister(stateMachineRuntimePersister);}

此示例還使用 ,這使得 更易于與多臺機器配合使用。 下面的清單顯示了如何創建 的實例:??DefaultStateMachineService????DefaultStateMachineService??

@Beanpublic StateMachineService stateMachineService(    StateMachineFactory stateMachineFactory,    StateMachineRuntimePersister stateMachineRuntimePersister) {  return new DefaultStateMachineService(stateMachineFactory, stateMachineRuntimePersister);}

以下清單顯示了驅動此示例中的邏輯:??StateMachineService??

private synchronized StateMachine getStateMachine(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

默認情況下,配置文件在 中處于啟用狀態。如果你想試試 其他后端,啟用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默認值, 但為了完整起見,我們將其包括在內):??jpa????application.yml????mongo????redis????jpa??

# 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 StateMachineRuntimePersister stateMachineRuntimePersister(    JpaStateMachineRepository jpaStateMachineRepository) {  return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);}

豆子使使用機器變得更加容易。 下面的清單顯示了如何創建這樣的 Bean:??StateMachineService??

@Beanpublic StateMachineService stateMachineService(    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 stateRepository;  @Autowired  private TransitionRepository 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。如下圖所示, 數據庫中的不同區域具有不同的上下文:??#??

數據 JPA 持久化

數據持久化示例演示如何狀態計算機概念 使用外部存儲庫中的持久計算機。此示例使用 帶有 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 StateMachineRuntimePersister stateMachineRuntimePersister(      JpaStateMachineRepository jpaStateMachineRepository) {    return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

以下示例演示如何使用非常相似的配置 為 MongoDB 創建一個 bean:

@Configuration@Profile("mongo")public static class MongoPersisterConfig {  @Bean  public StateMachineRuntimePersister stateMachineRuntimePersister(      MongoDbStateMachineRepository jpaStateMachineRepository) {    return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

以下示例演示如何使用非常相似的配置 為 Redis 創建一個 bean:

@Configuration@Profile("redis")public static class RedisPersisterConfig {  @Bean  public StateMachineRuntimePersister stateMachineRuntimePersister(      RedisStateMachineRepository jpaStateMachineRepository) {    return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository);  }}

可以使用配置方法配置為使用運行時持久性。 以下清單顯示了如何執行此操作:??StateMachine????withPersistence??

@Autowiredprivate StateMachineRuntimePersister stateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer config)    throws Exception {  config    .withPersistence()      .runtimePersister(stateMachineRuntimePersister);}

此示例還使用 ,這使得 更易于與多臺機器配合使用。 下面的清單顯示了如何創建 的實例:??DefaultStateMachineService????DefaultStateMachineService??

@Beanpublic StateMachineService stateMachineService(    StateMachineFactory stateMachineFactory,    StateMachineRuntimePersister stateMachineRuntimePersister) {  return new DefaultStateMachineService(stateMachineFactory, stateMachineRuntimePersister);}

以下清單顯示了驅動此示例中的邏輯:??StateMachineService??

private synchronized StateMachine getStateMachine(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

默認情況下,配置文件在 中處于啟用狀態。如果你想試試 其他后端,啟用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默認值, 但為了完整起見,我們將其包括在內):??jpa????application.yml????mongo????redis????jpa??

# 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??

附錄

附錄 A:支持內容

本附錄提供有關類和 本參考文檔中使用的材料。

本文檔中使用的類

以下清單顯示了本參考指南中使用的類:

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}

附錄 B:狀態機概念

本附錄提供有關狀態機的通用信息。

快速示例

假設我們有命名的狀態和命名的事件和 ,您可以定義狀態機的邏輯,如下圖所示:??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  StateMachine stateMachine;  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數字鍵盤。 鑒于鍵盤的左側和右側都可以在沒有 其他,它們必須具有完全不同的狀態,這意味著它們是 在不同的狀態機上運行。在狀態機術語中,主要部分 鍵盤是一個區域,數字鍵盤是另一個區域。

處理兩個不同的會有點不方便 狀態機作為完全獨立的實體,因為它們 仍然以某種方式一起工作。這種獨立性讓正交區域 在單個狀態內以多個同時狀態組合在一起 在狀態機中。

附錄 C:分布式狀態機技術論文

本附錄提供了有關以下內容的更詳細的技術文檔。 將 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事件發送到節點,所有節點報告狀態更改 從 到 。In1S21S22事件發送到節點,所有節點報告狀態更改 從 到 。Cn2S22S211事件發送到節點,所有節點報告狀態更改 從 到 。In5S211S212事件發送到節點,所有節點報告狀態更改 從 到 。Kn3S212S21我們通過隨機節點循環事件、、、和一次。ICIK

平行事件

多個分布式狀態機的一個邏輯問題是,如果 同一事件以完全相同的方式發送到多個狀態機 時間,則只有一個事件會導致分布式狀態 轉換。這是一個有點意料之中的場景,因為第一個狀態 能夠更改分布式狀態的計算機(對于此事件) 控制分布式轉換邏輯。實際上,所有其他 接收此相同事件的計算機以靜默方式丟棄該事件, 因為分布式狀態不再處于特定狀態 可以處理事件。

在下圖所示的測試中,我們演示了由 整個融合中的并行事件最終會導致 所有計算機的狀態更改一致:

在上圖中,我們使用與上一示例中使用的相同事件流 (孤立事件),不同之處在于事件始終是 發送到所有節點。

并發擴展狀態變量更改

擴展狀態機變量不保證在 任何給定時間,但是,在分布式狀態更改后,所有狀態機 在融合中應具有同步擴展狀態。

在此測試中,我們證明了擴展狀態的變化 一個分布式狀態機中的變量最終變成 在所有分布式狀態機中保持一致。 下圖顯示了此測試:

在上圖中:

事件被發送到事件變量具有值的節點。然后,所有節點都報告具有一個值為 .Jn5testVariablev1testVariablev1事件從變量重復到 ,執行相同的檢查。Jv2v8

分區容錯

我們需要始終假設,遲早,集群中的事物 變壞了,無論是 Zookeeper 實例的崩潰,還是狀態 機器崩潰,或網絡問題,如“大腦分裂”。(腦裂是 現有集群成員被隔離的情況,以便只有 部分主機能夠看到彼此)。通常的情況是 大腦分裂造成少數和多數分裂 合奏,使得少數人的主持人不能參與合奏 直到網絡狀態已恢復。

在下面的測試中,我們證明了各種類型的大腦分裂 融合最終會導致所有 分布式狀態機。

有兩種情況的大腦直接分裂在一個 位置和實例所在的網絡 分成兩半(假設每個連接到 本地實例):??Zookeeper????Statemachine????Statemachine????Zookeeper??

如果目前的動物園管理員負責人保持多數,則所有客戶 連接到大多數人保持正常運行。如果目前的動物園管理員負責人是少數,則所有客戶 斷開與它的連接,然后嘗試重新連接到上一個 少數派成員已成功加入現有多數派 整體。

在我們目前的Jepsen測試中,我們無法分離Zookeeper裂腦 領導者被留在多數或少數之間的場景,所以我們需要 多次運行測試以完成此情況。

在下面的圖中,我們將狀態機錯誤狀態映射到 ,以指示狀態機處于錯誤狀態,而不是 正常狀態。在解釋圖表狀態時,請記住這一點。??error??

在第一個測試中,我們展示了當現有的動物園管理員領導者 保持大多數,五分之三的機器繼續保持原樣。 下圖顯示了此測試:

在上圖中:

第一個事件 將發送到所有計算機,從而導致狀態更改為 。CS211杰普森克星導致大腦分裂,從而導致分裂 的和。節點留在少數,并且 節點構建新的健康多數。節點中的 多數人保持正常運行,但少數人中的節點 進入錯誤狀態。n1/n2/n5n3/n4n3/n4n1/n2/n5Jepsen修復了網絡,一段時間后,節點加入 返回到融合并同步其分布式狀態。n3/n4最后,將事件發送到所有狀態機,以確保 工作正常。此狀態更改導致返回到狀態 。K1S21

在第二個測試中,我們表明,當現有的動物園管理員領導者是 保持在少數,所有機器都出錯了。 下圖顯示了第二個測試:

在上圖中:

第一個事件 將發送到導致狀態更改為 的所有計算機。CS211杰普森克星導致大腦分裂,從而導致分裂 這樣現有的領導人就保持在少數和所有人中 實例與整體斷開連接。ZookeeperJepsen修復了網絡,一段時間后,所有節點都加入了 返回到融合并同步其分布式狀態。最后,將事件發送到所有狀態機,以確保 正常工作。此狀態更改導致返回到狀態 。K1S21

崩潰和聯接容錯

在這個測試中,我們證明了殺死現有的狀態機 然后將新實例重新加入到融合中,使 分布式狀態正常,新加入的狀態機同步 他們的狀態正確。 下圖顯示了崩潰和聯接容差測試:

在此測試中,不會在第一個 和 最后一個之間檢查狀態。 因此,該圖顯示了兩者之間的一條平線。檢查狀態 狀態更改在 和 之間發生的確切位置。??X????X????S21????S211??

在上圖中:

所有狀態機都從初始狀態 () 轉換為 狀態,以便我們可以在連接期間測試正確的狀態同步。S21S211??X??標記特定節點何時崩潰并啟動。同時,我們請求所有機器的狀態并繪制結果。最后,我們做一個簡單的過渡回到 from 以 確保所有狀態機仍正常運行。S21S211

開發人員文檔

本附錄為開發人員提供了一般信息,他們可能 想要貢獻或其他想要了解狀態的人 機器工作或理解其內部概念。

狀態機配置模型

??StateMachineModel??和其他相關的SPI類是一個抽象 在各種配置和工廠類之間。這也允許 其他人更容易集成以構建狀態機。

如下面的清單所示,您可以通過構建模型來實例化狀態機 使用配置數據類,然后要求工廠構建一個 狀態機:

// setup configuration dataConfigurationData configurationData = 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();

附錄D:反應堆遷移指南

工作的主要任務是內部和外部移動和改變 我們盡可能多地從命令式代碼進入一個響應式世界。這意味著一些 的主接口添加了一個新的 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??

Message message = 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

狀態機執行和狀態操作調度已被完全取代,有利于或反應器執行和調度。??TaskExecutor????TaskScheduler??

基本上,在兩個地方需要在主線程之外執行,首先是需要可取消的狀態操作,其次是應該?始終獨立執行。目前,我們選擇只使用Reactor來做這些應該會給出相對好的結果,因為它 嘗試自動使用系統中可用數量的 CPU 內核。??Schedulers.parallel()??

反應式示例

雖然大多數示例仍然相同,但我們已經徹底修改了其中的一些示例,并且 創建了一些新的:

調諧器反應性旋轉門反應式旋轉門反應

標簽: 歷史記錄 可以使用

上一篇:世界熱推薦:如何在 Linux 下使用 TC 優雅的實現網絡限流
下一篇:天天即時:Spring Statemachine狀態機的概念(四)