Spring Statemachine状态机的概念

Spring Statemachine(SSM)是一个框架,可以让应用程序开发人员 将传统的状态机概念与 Spring 应用程序结合使用。SSM 提供以下功能:

易于使用的平面(单级)状态机,适用于简单用例。分层状态机结构,简化复杂状态 配置。状态机区域提供更复杂的状态 配置。触发器、转换、防护和操作的用法。类型安全的配置适配器。状态机事件侦听器。Spring IoC 集成,用于将 bean 与状态机相关联。

在继续之前,我们建议您阅读附录术语表和状态机速成课程,以大致了解什么是状态机。 文档的其余部分希望您 熟悉状态机概念。

背景

状态机之所以强大,是因为它们的行为始终得到保证 由于操作方式,一致性和相对容易调试 当机器启动时,规则是一成不变的。这个想法是你的 应用程序现在处于并且可能存在于有限数量的状态中。然后一些东西 发生将您的应用程序从一个状态带到下一个状态。 状态机由触发器驱动,触发器基于 事件或计时器。

在你之外设计高级逻辑要容易得多 应用程序,然后与各种状态机进行交互 不同的方式。您可以通过以下方式与状态机交互 发送事件、侦听状态机执行的操作或请求 当前状态。

传统上,状态机在以下情况下添加到现有项目中: 开发人员意识到代码库开始看起来像一个盘子 满满的意大利面。意大利面条代码看起来像一个永无止境的分层代码 IF、ELSE 和 BREAK 子句的结构,以及编译器应该 当事情开始看起来太复杂时,请开发人员回家。

使用场景

在以下情况下,项目非常适合使用状态机:

可以将应用程序或其结构的一部分表示为状态。您希望将复杂的逻辑拆分为较小的可管理任务。应用程序已经遇到并发问题(例如) 异步发生的事情。

在以下情况下,您已经在尝试实现状态机:

使用布尔标志或枚举对情况进行建模。具有仅对某些部分有意义的变量 应用程序生命周期。循环遍历一个if-else结构(或者更糟糕的是,多个这样的结构), 检查特定标志或 设置枚举,然后进一步例外,当某些 标志和枚举的组合存在或不存在。开始

如果你刚刚开始使用Spring Statemachine, 这是适合您的部分!在这里,我们回答基本 “”、“”和“”问题。我们从温柔的 弹簧状态机简介。然后我们建立我们的 第一个弹簧状态机应用并讨论一些 我们前进的核心原则。??what?????how?????why???

系统要求

Spring 状态机 3.2.0 构建并测试 JDK 8(所有工件都具有JDK 7兼容性)和Spring。 框架 5.3.19.它不需要任何其他 在其核心系统中的 Spring 框架之外的依赖项。

其他可选部分(如使用分布式状态)依赖于 Zookeeper,而状态机示例具有依赖项 on 和 ,它们拉取其他依赖项 超越框架本身。此外,可选的安全和数据访问功能具有 依赖于 Spring 安全性和 Spring 数据模块。??spring-shell????spring-boot??

模块

下表描述了可用于 Spring 状态机的模块。

模块

描述

??spring-statemachine-core??

弹簧状态机的核心系统。

??spring-statemachine-recipes-common??

不需要核心外部依赖项的常见配方 框架。

??spring-statemachine-kryo??

??Kryo??弹簧状态机的序列化程序。

??spring-statemachine-data-common??

的通用支持模块。??Spring Data??

??spring-statemachine-data-jpa??

的支持模块。??Spring Data JPA??

??spring-statemachine-data-redis??

的支持模块。??Spring Data Redis??

??spring-statemachine-data-mongodb??

的支持模块。??Spring Data MongoDB??

??spring-statemachine-zookeeper??

分布式状态机的 Zookeeper 集成。

??spring-statemachine-test??

状态机测试支持模块。

??spring-statemachine-cluster??

春季云集群支持模块。 请注意,Spring Cloud Cluster 已被 Spring Integration 取代。

??spring-statemachine-uml??

使用 Eclipse Papyrus 进行 UI UML 建模的支持模块。

??spring-statemachine-autoconfigure??

弹簧引导的支持模块。

??spring-statemachine-bom??

物料清单 pom.

??spring-statemachine-starter??

弹簧启动器。

使用 Gradle

下面的清单显示了通过在https://start.spring.io选择各种设置而创建的典型文件:??build.gradle??

buildscript { ext { springBootVersion = ‘2.6.7’ } repositories { mavenCentral() maven { url “https://repo.spring.io/snapshot” } maven { url “https://repo.spring.io/milestone” } } dependencies { classpath(“org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}”) }}apply plugin: ‘java’apply plugin: ‘eclipse’apply plugin: ‘org.springframework.boot’apply plugin: ‘io.spring.dependency-management’group = ‘com.example’version = ‘0.0.1-SNAPSHOT’sourceCompatibility = 1.8repositories { mavenCentral() maven { url “https://repo.spring.io/snapshot” } maven { url “https://repo.spring.io/milestone” }}ext { springStatemachineVersion = ‘3.2.0’}dependencies { compile(‘org.springframework.statemachine:spring-statemachine-starter’) testCompile(‘org.springframework.boot:spring-boot-starter-test’)}dependencyManagement { imports { mavenBom “org.springframework.statemachine:spring-statemachine-bom:${springStatemachineVersion}” }}

替换为要使用的版本。??0.0.1-SNAPSHOT??

使用正常的项目结构,可以使用以下命令生成此项目:

# ./gradlew clean build

预期的春季靴子包装的胖罐将是。??build/libs/demo-0.0.1-SNAPSHOT.jar??

您不需要“libs里程碑”和存储库 生产开发。??libs-snapshot??

使用马文

以下示例显示了一个典型文件,该文件是通过在 https://start.spring.io选择各种选项创建的:??pom.xml??

<?xml version=”1.0″ encoding=”UTF-8″?><project xmlns=”http://maven.apache.org/POM/4.0.0″ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>gs-statemachine</name> <description>Demo project for Spring Statemachine</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-statemachine.version>3.2.0</spring-statemachine.version> </properties> <dependencies> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-bom</artifactId> <version>${spring-statemachine.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories></project>

替换为要使用的版本。??0.0.1-SNAPSHOT??

使用正常的项目结构,可以使用以下命令生成此项目:

# mvn clean package

预期的春季靴包装的胖罐将是。??target/demo-0.0.1-SNAPSHOT.jar??

您不需要 和 存储库 生产开发。??libs-milestone????libs-snapshot??

开发您的第一个弹簧状态机应用程序

您可以从创建一个简单的 Spring Boot 类开始 实现.以下示例演示如何执行此操作:??Application????CommandLineRunner??

@SpringBootApplicationpublic class Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

然后,您需要添加状态和事件,如以下示例所示:

public enum States { SI, S1, S2}public enum Events { E1, E2}

然后,您需要添加状态机配置,如以下示例所示:

@Configuration@EnableStateMachinepublic class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withConfiguration() .autoStartup(true) .listener(listener()); } @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.SI) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.SI).target(States.S1).event(Events.E1) .and() .withExternal() .source(States.S1).target(States.S2).event(Events.E2); } @Bean public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { @Override public void stateChanged(State<States, Events> from, State<States, Events> to) { System.out.println(“State change to ” + to.getId()); } }; }}

然后你需要实现和自动连接. 以下示例演示如何执行此操作:??CommandLineRunner????StateMachine??

@Autowiredprivate StateMachine<States, Events> stateMachine;@Overridepublic void run(String… args) throws Exception { stateMachine.sendEvent(Events.E1); stateMachine.sendEvent(Events.E2);}

根据是使用 或 构建应用程序,具体取决于是使用 或 构建应用程序。 您可以分别使用 或 来运行它。??Gradle????Maven????java -jar build/libs/gs-statemachine-0.1.0.jar????java -jar target/gs-statemachine-0.1.0.jar??

此命令的结果应该是正常的 Spring 引导输出。 但是,您还应该找到以下行:

State change to SIState change to S1State change to S2

这些线表示您构建的机器 正在从一个州移动到另一个州,这是应该的。

最新消息在 1.1 中

Spring 状态机 1.1 专注于安全性和更好的 与 Web 应用程序的互操作性。它包括以下内容:

添加了对 Spring 安全性的全面支持。请参阅状态机安全性。与“@WithStateMachine”的上下文集成已经大大增加 增强。请参阅上下文集成。StateContext现在是一等公民,让你 与状态机交互。请参阅使用状态上下文。内置的持久性功能已得到增强 支持瑞迪斯。请参阅使用 Redis。新功能有助于持久化操作。请参阅使用StateMachinePersister。配置模型类现在位于公共 API 中。基于计时器的事件中的新功能。新的伪状态。请参阅结状态。Junction新的出口点和入口点伪状态。请参阅出口和入口点状态。配置模型验证程序。新示例。请参阅安全和事件服务。使用 Eclipse Papyrus 的 UI 建模支持。请参见Eclipse 建模支持。在 1.2 中

Spring 状态机 1.2 专注于通用增强,更好 UML 支持以及与外部配置存储库的集成。 它包括以下内容:

支持 UML 子机。请参见使用子计算机引用。一个新的存储库抽象,将计算机配置保存在 外部存储库。请参阅存储库支持。对国家行动的新支持。请参阅状态操作。新的转换错误操作概念。请参阅转换操作错误处理。新的操作错误概念。请参阅状态操作错误处理。Spring 引导支持的初始工作。请参阅弹簧引导支持。支持跟踪和监视。请参阅监视状态机。在 1.2.8 中

Spring 状态机 1.2.8 包含的功能比平时多一点 在点版本中看不到,但这些更改不值得分叉 弹簧状态机 1.3.它包括以下内容:

JPA 实体类已更改表名。请参阅JPA。新示例。请参阅数据保留。用于持久性的新实体类。请参阅存储库持久性。过渡冲突策略。请参阅配置通用设置在 2.0 中

Spring Statemachine 2.0 专注于 Spring Boot 2.x 支持。

在 2.0.0 中

Spring 状态机 2.0.0 包括以下内容:

监视和跟踪的格式已更改。请参阅监视和跟踪。模块已重命名为 。spring-statemachine-bootspring-statemachine-autoconfigure在 3.0 中

Spring 状态机 3.0.0 专注于添加反应式支持。从 移动到 是 介绍了一些重大更改,详见反应堆迁移指南。??2.x????3.x??

我们已经弃用了所有将在某个时候被删除的阻止方法 在将来的版本中。??3.0.x??

请仔细阅读附录反应堆迁移指南?,因为它将指导您 通过迁移到我们内部未处理的情况的过程。??3.x??

此时,大多数文档已更改为展示反应式接口 虽然我们仍然为仍在使用旧阻止方法的用户保留一些注释。

使用弹簧状态机

参考文档的这一部分解释了核心功能 Spring 状态机提供给任何基于 Spring 的应用程序。

它包括以下主题:

状态机配置描述了通用配置支持。状态机 ID描述机器 ID 的使用。状态机工厂描述了通用状态机工厂支持。使用延迟事件描述了延迟事件支持。使用作用域介绍了作用域支持。使用操作介绍了操作支持。使用防护描述了防护支持。使用扩展状态描述了扩展状态支持。使用StateContext描述了状态上下文支持。触发转换描述了触发器的使用。侦听状态机事件描述了状态机侦听器的使用。上下文集成描述了通用的 Spring 应用程序上下文支持。使用StateMachineAccessor描述了状态机内部访问器支持。使用StateMachineInterceptor描述了状态机错误处理支持。状态机安全性描述状态机安全性支持。状态机错误处理描述了状态机拦截器支持。状态机服务描述状态机服务支持。持久化状态机描述了状态机持久化支持。SpringBoot Support 描述了 Spring Boot支持。监视状态机描述了监视和跟踪支持。使用分布式状态描述了分布式状态机支持。测试支持描述了状态机测试支持。Eclipse建模支持描述了状态机 UML 建模支持。存储库支持描述了状态机存储库配置支持。状态机配置

使用状态机时的常见任务之一是设计其 运行时配置。本章重点介绍春天如何 状态机的配置以及它如何利用 Spring 的轻量级 IoC 容器可简化应用程序内部,使其更多 管理。

本节中的配置示例功能不完整。那是 您始终需要同时定义状态和转换。 否则,状态机配置的格式不正确。我们有 通过保留其他需要的部分,简单地使代码片段不那么冗长 外。

使用批注??enable??

我们使用两个熟悉的 Spring启用码注释来简化配置:和 。 这些批注在放置在类中时,启用 状态机所需的一些基本功能。??@EnableStateMachine????@EnableStateMachineFactory????@Configuration??

当您需要配置来创建 的实例。通常,类扩展适配器 ( 或 ),其中 允许您重写配置回调方法。我们自动 检测是否使用这些适配器类并修改运行时配置 逻辑相应。??@EnableStateMachine????StateMachine????@Configuration????EnumStateMachineConfigurerAdapter????StateMachineConfigurerAdapter??

当您需要配置来创建 的实例。??@EnableStateMachineFactory????StateMachineFactory??

以下各节显示了这些用法示例。

配置状态

我们将在本指南的后面介绍更复杂的配置示例,但是 我们首先从简单的事情开始。对于最简单的状态 机器,您可以使用和定义 ,然后选择初始状态和可选结束状态。??EnumStateMachineConfigurerAdapter??

@Configuration@EnableStateMachinepublic class Config1Enums extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); }}

还可以使用字符串而不是枚举作为状态和 事件使用 ,如下例所示。最 的配置示例 UES 枚举,但一般来说, 可以交换字符串和枚举。??StateMachineConfigurerAdapter??

@Configuration@EnableStateMachinepublic class Config1Strings extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“S1”) .end(“SF”) .states(new HashSet<String>(Arrays.asList(“S1″,”S2″,”S3″,”S4”))); }}

使用枚举带来了一组更安全的状态和事件类型,但 限制编译时可能的组合。字符串没有这个 限制,并允许您使用更动态的方式来构建状态 机器配置,但不允许相同级别的安全。

配置分层状态

可以使用多个调用来定义分层状态,其中可用于指示这些 特定状态是其他某个状态的子状态。 以下示例演示如何执行此操作:??withStates()????parent()??

@Configuration@EnableStateMachinepublic class Config2 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .state(States.S1) .and() .withStates() .parent(States.S1) .initial(States.S2) .state(States.S2); }}配置区域

没有特殊的配置方法来标记集合 状态是正交状态的一部分。简单地说,正交 当同一分层状态机具有多个集合时创建状态 的状态,每个状态都有一个初始状态。因为个人状态 机器只能有一个初始状态,必须有多个初始状态 意味着特定状态必须具有多个独立区域。 以下示例演示如何定义区域:

@Configuration@EnableStateMachinepublic class Config10 extends EnumStateMachineConfigurerAdapter<States2, Events> { @Override public void configure(StateMachineStateConfigurer<States2, Events> states) throws Exception { states .withStates() .initial(States2.S1) .state(States2.S2) .and() .withStates() .parent(States2.S2) .initial(States2.S2I) .state(States2.S21) .end(States2.S2F) .and() .withStates() .parent(States2.S2) .initial(States2.S3I) .state(States2.S31) .end(States2.S3F); }}

当保留具有区域或通常的计算机时 依靠任何功能来重置机器,您可能需要 以拥有区域的专用 ID。默认情况下,此 ID 是生成的 UUID。如以下示例所示,具有 一种名为的方法,用于设置区域的 ID:??StateConfigurer????region(String id)??

@Configuration@EnableStateMachinepublic class Config10RegionId extends EnumStateMachineConfigurerAdapter<States2, Events> { @Override public void configure(StateMachineStateConfigurer<States2, Events> states) throws Exception { states .withStates() .initial(States2.S1) .state(States2.S2) .and() .withStates() .parent(States2.S2) .region(“R1”) .initial(States2.S2I) .state(States2.S21) .end(States2.S2F) .and() .withStates() .parent(States2.S2) .region(“R2”) .initial(States2.S3I) .state(States2.S31) .end(States2.S3F); }}配置过渡

我们支持三种不同类型的转换:、 和 。转换由信号触发 (这是发送到状态机的事件)或计时器。 下面的示例演示如何定义所有三种类型的转换:??external????internal????local??

@Configuration@EnableStateMachinepublic class Config3 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1).target(States.S2) .event(Events.E1) .and() .withInternal() .source(States.S2) .event(Events.E2) .and() .withLocal() .source(States.S2).target(States.S3) .event(Events.E3); }}配置防护

可以使用防护来保护状态转换。您可以使用界面 执行方法有权访问 . 以下示例演示如何执行此操作:??Guard????StateContext??

@Configuration@EnableStateMachinepublic class Config4 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1).target(States.S2) .event(Events.E1) .guard(guard()) .and() .withExternal() .source(States.S2).target(States.S3) .event(Events.E2) .guardExpression(“true”); } @Bean public Guard<States, Events> guard() { return new Guard<States, Events>() { @Override public boolean evaluate(StateContext<States, Events> context) { return true; } }; }}

在前面的示例中,我们使用了两种不同类型的防护配置。首先,我们 创建了一个简单的豆子,并将其附加到 状态和 .??Guard????S1????S2??

其次,我们使用 SPeL 表达式作为守卫来指示 表达式必须返回一个值。在幕后,这个 基于表达式的防护是一个 .我们将其附加到 状态和 .两个后卫 始终计算为 。??BOOLEAN????SpelExpressionGuard????S2????S3????true??

配置操作

您可以定义要使用转换和状态执行的操作。 操作始终作为转换的结果运行 源自触发器。以下示例演示如何定义操作:

@Configuration@EnableStateMachinepublic class Config51 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1) .target(States.S2) .event(Events.E1) .action(action()); } @Bean public Action<States, Events> action() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { // do something } }; }}

在前面的示例中,单个定义为命名和关联的 Bean 从 过渡到 . 以下示例演示如何多次使用一个操作:??Action????action????S1????S2??

@Configuration@EnableStateMachinepublic class Config52 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1, action()) .state(States.S1, action(), null) .state(States.S2, null, action()) .state(States.S2, action()) .state(States.S3, action(), action()); } @Bean public Action<States, Events> action() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { // do something } }; }}

通常,您不会为不同的实例定义相同的实例 阶段,但我们在这里这样做是为了不给代码带来太大的噪音 片段。??Action??

在前面的示例中,单个由命名和关联的 Bean 定义 带有状态、 和 。我们需要澄清这里发生了什么:??Action????action????S1????S2????S3??

我们为初始状态定义了一个操作,.S1我们为状态定义了一个进入操作,并将退出操作留空。S1我们为状态定义了一个退出操作,并将进入操作留空。S2我们为状态 定义了单个状态操作。S2我们为状态 定义了进入和退出操作。S3请注意,状态与 and 函数一起使用两次。仅当您要定义进入或退出时才需要执行此操作 具有初始状态的操作。S1initial()state()

使用函数定义操作仅运行特定的 启动状态机或子状态时的操作。此操作 是仅运行一次的初始化操作。定义的操作 如果状态机转换回来,则运行 with 并在初始状态和非初始状态之间前进。??initial()????state()??

状态操作

与进入和退出相比,状态操作的运行方式不同 操作,因为执行发生在进入状态之后 如果在特定操作之前发生状态退出,则可以取消 已经完成。

状态操作使用正常的反应流执行,方法是订阅 反应器的默认并行调度程序。这意味着,无论你在你的 行动,你需要能够抓住,或者更一般地说, 定期检查是否中断。??InterruptedException????Thread??

以下示例显示了使用默认值 的典型配置 , 当正在运行的任务的状态为完成时,将立即取消该任务:??IMMEDIATE_CANCEL??

@Configuration@EnableStateMachinestatic class Config1 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withConfiguration() .stateDoActionPolicy(StateDoActionPolicy.IMMEDIATE_CANCEL); } @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“S1”) .state(“S2”, context -> {}) .state(“S3”); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source(“S1”) .target(“S2”) .event(“E1”) .and() .withExternal() .source(“S2”) .target(“S3”) .event(“E2”); }}

您可以将策略设置为与全局超时一起 对于每台机器。这会更改状态行为以等待操作完成 在请求取消之前。以下示例演示如何执行此操作:??TIMEOUT_CANCEL??

@Overridepublic void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withConfiguration() .stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL) .stateDoActionPolicyTimeout(10, TimeUnit.SECONDS);}

如果直接使机器进入某种状态,使事件标头 可用于特定操作,也可以使用专用 用于设置特定超时的事件标头(在 中定义)。 为此,可以使用保留的标头值。以下示例演示如何执行此操作:??Event????millis????StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT??

@AutowiredStateMachine<String, String> stateMachine;void sendEventUsingTimeout() { stateMachine .sendEvent(Mono.just(MessageBuilder .withPayload(“E1”) .setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000) .build())) .subscribe();}转换操作错误处理

您始终可以手动捕获异常。但是,使用 转换时,您可以定义一个错误操作,如果 引发异常。然后,异常可从传递给该操作中获取。以下示例演示如何创建状态 处理异常:??StateContext??

@Configuration@EnableStateMachinepublic class Config53 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1) .target(States.S2) .event(Events.E1) .action(action(), errorAction()); } @Bean public Action<States, Events> action() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { throw new RuntimeException(“MyError”); } }; } @Bean public Action<States, Events> errorAction() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { // RuntimeException(“MyError”) added to context Exception exception = context.getException(); exception.getMessage(); } }; }}

如果需要,您可以为每个操作手动创建类似的逻辑。 以下示例演示如何执行此操作:

@Overridepublic void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1) .target(States.S2) .event(Events.E1) .action(Actions.errorCallingAction(action(), errorAction()));}状态操作错误处理

还可以使用类似于处理状态转换中错误的逻辑的逻辑 用于进入状态和退出状态。

对于这些情况, 具有称为 、 和 的方法。这些方法定义一个动作和一个正常(非错误)。 下面的示例演示如何使用所有三种方法:??StateConfigurer????stateEntry????stateDo????stateExit????error????action??

@Configuration@EnableStateMachinepublic class Config55 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .stateEntry(States.S2, action(), errorAction()) .stateDo(States.S2, action(), errorAction()) .stateExit(States.S2, action(), errorAction()) .state(States.S3); } @Bean public Action<States, Events> action() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { throw new RuntimeException(“MyError”); } }; } @Bean public Action<States, Events> errorAction() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { // RuntimeException(“MyError”) added to context Exception exception = context.getException(); exception.getMessage(); } }; }}配置伪状态

伪状态配置通常通过配置状态和 转换。伪状态自动添加到状态机中,作为 国家。

初始状态

可以使用该方法将特定状态标记为初始状态。此初始操作很好,例如,初始化 扩展状态变量。下面的示例演示如何使用该方法:??initial()????initial()??

@Configuration@EnableStateMachinepublic class Config11 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1, initialAction()) .end(States.SF) .states(EnumSet.allOf(States.class)); } @Bean public Action<States, Events> initialAction() { return new Action<States, Events>() { @Override public void execute(StateContext<States, Events> context) { // do something initially } }; }}终止状态

可以使用该方法将特定状态标记为结束状态。 对于每个单独的子计算机或区域,您最多只能执行此操作一次。 下面的示例演示如何使用该方法:??end()????end()??

@Configuration@EnableStateMachinepublic class Config1Enums extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); }}州历史

您可以为每个单独的状态机定义一次状态历史记录。 您需要选择其状态标识符并设置 或 。以下示例使用:??History.SHALLOW????History.DEEP????History.SHALLOW??

@Configuration@EnableStateMachinepublic class Config12 extends EnumStateMachineConfigurerAdapter<States3, Events> { @Override public void configure(StateMachineStateConfigurer<States3, Events> states) throws Exception { states .withStates() .initial(States3.S1) .state(States3.S2) .and() .withStates() .parent(States3.S2) .initial(States3.S2I) .state(States3.S21) .state(States3.S22) .history(States3.SH, History.SHALLOW); } @Override public void configure(StateMachineTransitionConfigurer<States3, Events> transitions) throws Exception { transitions .withHistory() .source(States3.SH) .target(States3.S22); }}

此外,如前面的示例所示,您可以选择定义默认值 在同一台机器中从历史状态转换为状态顶点。 例如,如果计算机具有 从未输入过 – 因此,没有历史记录可用。如果默认 未定义状态转换,则正常进入区域 做。如果计算机的历史记录 最终状态。

选择状态

需要在状态和过渡中定义选择才能工作 适当地。可以使用该方法将特定状态标记为选择状态。当转换 为此选择进行了配置。??choice()??

可以使用 配置转换,其中定义源 状态和结构,相当于正常的.使用 和 ,您可以只指定一个守卫 就像您将使用带有子句的条件一样。??withChoice()????first/then/last????if/elseif/else????first????then????if/elseif??

过渡需要能够存在,因此必须确保使用 . 否则,配置格式不正确。以下示例演示如何定义 选择状态:??last??

@Configuration@EnableStateMachinepublic class Config13 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.SI) .choice(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withChoice() .source(States.S1) .first(States.S2, s2Guard()) .then(States.S3, s3Guard()) .last(States.S4); } @Bean public Guard<States, Events> s2Guard() { return new Guard<States, Events>() { @Override public boolean evaluate(StateContext<States, Events> context) { return false; } }; } @Bean public Guard<States, Events> s3Guard() { return new Guard<States, Events>() { @Override public boolean evaluate(StateContext<States, Events> context) { return true; } }; }}

操作可以在传入和传出转换的情况下运行 选择伪状态。如以下示例所示,一个虚拟 lambda 定义导致选择状态和一个类似假人的动作 Lambda 操作是为一个传出转换定义的(其中也 定义错误操作):

@Configuration@EnableStateMachinepublic class Config23 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.SI) .choice(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.SI) .action(c -> { // action with SI-S1 }) .target(States.S1) .and() .withChoice() .source(States.S1) .first(States.S2, c -> { return true; }) .last(States.S3, c -> { // action with S1-S3 }, c -> { // error callback for action S1-S3 }); }}

联结具有相同的 api 格式,这意味着可以定义操作 同样地。

结状态

您需要在状态和转换中定义一个交汇点才能使其正常工作 适当地。可以使用该方法将特定状态标记为选择状态。当转换 为此选择进行了配置。??junction()??

您可以通过定义源的位置来配置转换 状态和结构(相当于普通)。使用 和 ,可以将守卫指定为 您将使用带有子句的条件。??withJunction()????first/then/last????if/elseif/else????first????then????if/elseif??

过渡需要能够存在,因此必须确保使用 . 否则,配置格式不正确。 以下示例使用联结:??last??

@Configuration@EnableStateMachinepublic class Config20 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.SI) .junction(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withJunction() .source(States.S1) .first(States.S2, s2Guard()) .then(States.S3, s3Guard()) .last(States.S4); } @Bean public Guard<States, Events> s2Guard() { return new Guard<States, Events>() { @Override public boolean evaluate(StateContext<States, Events> context) { return false; } }; } @Bean public Guard<States, Events> s3Guard() { return new Guard<States, Events>() { @Override public boolean evaluate(StateContext<States, Events> context) { return true; } }; }}

选择和交汇点之间的区别纯粹是学术性的,因为两者都是 用结构实现。然而,从理论上讲,基于 在 UML 建模中,只允许一个传入转换,而允许多个传入转换。在代码级别, 功能几乎相同。??first/then/last????choice????junction??

分叉状态

您必须在状态和转换中定义分叉才能正常工作 适当地。可以使用该方法将特定状态标记为选择状态。当转换 为此分叉配置。??fork()??

目标状态必须是超级状态或即时状态 地区。使用超级状态作为目标将所有区域带入 初始状态。定位单个州可提供更可控的进入 进入区域。以下示例使用分叉:

@Configuration@EnableStateMachinepublic class Config14 extends EnumStateMachineConfigurerAdapter<States2, Events> { @Override public void configure(StateMachineStateConfigurer<States2, Events> states) throws Exception { states .withStates() .initial(States2.S1) .fork(States2.S2) .state(States2.S3) .and() .withStates() .parent(States2.S3) .initial(States2.S2I) .state(States2.S21) .state(States2.S22) .end(States2.S2F) .and() .withStates() .parent(States2.S3) .initial(States2.S3I) .state(States2.S31) .state(States2.S32) .end(States2.S3F); } @Override public void configure(StateMachineTransitionConfigurer<States2, Events> transitions) throws Exception { transitions .withFork() .source(States2.S2) .target(States2.S22) .target(States2.S32); }}加入状态

必须在状态和转换中定义联接才能正常工作 适当地。可以使用该方法将特定状态标记为选择状态。此状态不需要匹配源状态或 转换配置中的目标状态。??join()??

您可以选择在所有源状态时转换的目标状态 已加入。如果使用状态托管区域作为源,则结束 区域的状态用作联接。否则,您可以选择任何 来自某个地区的状态。以下示例使用连接:

@Configuration@EnableStateMachinepublic class Config15 extends EnumStateMachineConfigurerAdapter<States2, Events> { @Override public void configure(StateMachineStateConfigurer<States2, Events> states) throws Exception { states .withStates() .initial(States2.S1) .state(States2.S3) .join(States2.S4) .state(States2.S5) .and() .withStates() .parent(States2.S3) .initial(States2.S2I) .state(States2.S21) .state(States2.S22) .end(States2.S2F) .and() .withStates() .parent(States2.S3) .initial(States2.S3I) .state(States2.S31) .state(States2.S32) .end(States2.S3F); } @Override public void configure(StateMachineTransitionConfigurer<States2, Events> transitions) throws Exception { transitions .withJoin() .source(States2.S2F) .source(States2.S3F) .target(States2.S4) .and() .withExternal() .source(States2.S4) .target(States2.S5); }}

您还可以让多个过渡源自 加入状态。在这种情况下,我们建议您使用防护装置并定义您的防护装置 这样在任何给定时间只有一个守卫的计算结果。否则 转换行为是不可预测的。如以下示例所示,其中守卫 检查扩展状态是否有变量:??TRUE??

@Configuration@EnableStateMachinepublic class Config22 extends EnumStateMachineConfigurerAdapter<States2, Events> { @Override public void configure(StateMachineStateConfigurer<States2, Events> states) throws Exception { states .withStates() .initial(States2.S1) .state(States2.S3) .join(States2.S4) .state(States2.S5) .end(States2.SF) .and() .withStates() .parent(States2.S3) .initial(States2.S2I) .state(States2.S21) .state(States2.S22) .end(States2.S2F) .and() .withStates() .parent(States2.S3) .initial(States2.S3I) .state(States2.S31) .state(States2.S32) .end(States2.S3F); } @Override public void configure(StateMachineTransitionConfigurer<States2, Events> transitions) throws Exception { transitions .withJoin() .source(States2.S2F) .source(States2.S3F) .target(States2.S4) .and() .withExternal() .source(States2.S4) .target(States2.S5) .guardExpression(“!extendedState.variables.isEmpty()”) .and() .withExternal() .source(States2.S4) .target(States2.SF) .guardExpression(“extendedState.variables.isEmpty()”); }}出口和入口点状态

您可以使用出口和进入点进行更受控的退出和进入 从和进入子机器。 下面的示例使用 and 方法来定义入口点:??withEntry????withExit??

@Configuration@EnableStateMachinestatic class Config21 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“S1”) .state(“S2”) .state(“S3”) .and() .withStates() .parent(“S2”) .initial(“S21”) .entry(“S2ENTRY”) .exit(“S2EXIT”) .state(“S22”); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source(“S1”).target(“S2”) .event(“E1”) .and() .withExternal() .source(“S1”).target(“S2ENTRY”) .event(“ENTRY”) .and() .withExternal() .source(“S22”).target(“S2EXIT”) .event(“EXIT”) .and() .withEntry() .source(“S2ENTRY”).target(“S22”) .and() .withExit() .source(“S2EXIT”).target(“S3”); }}

如前所示,您需要将特定状态标记为存在和状态。然后创建到这些状态的正常过渡 并指定 和 ,其中这些状态 分别退出和进入。??exit????entry????withExit()????withEntry()??

配置通用设置

您可以使用 设置部分通用状态机配置。有了它,您可以设置和自动启动标志 对于状态机。它还允许您注册实例, 配置转换冲突策略和区域执行策略。 以下示例演示如何使用:??ConfigurationConfigurer????BeanFactory????StateMachineListener????ConfigurationConfigurer??

@Configuration@EnableStateMachinepublic class Config17 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withConfiguration() .autoStartup(true) .machineId(“myMachineId”) .beanFactory(new StaticListableBeanFactory()) .listener(new StateMachineListenerAdapter<States, Events>()) .transitionConflictPolicy(TransitionConflictPolicy.CHILD) .regionExecutionPolicy(RegionExecutionPolicy.PARALLEL); }}

默认情况下,状态机标志处于禁用状态,因为所有 处理子状态的实例由状态机本身控制 并且无法自动启动。此外,离开更安全 是否应启动计算机 自动或不自动给用户。此标志仅控制 顶级状态机。??autoStartup??

在配置类中进行设置只是为了方便那些 你想或需要在那里做。??machineId??

注册实例也部分用于 方便,但如果您想在 状态机生命周期,例如获取状态机的通知 启动和停止事件。请注意,您无法侦听状态 计算机的启动事件(如果已启用),除非您注册了侦听器 在配置阶段。??StateMachineListener????autoStartup??

您可以在多个时使用 可以选择过渡路径。一个常见的用例是当 计算机包含从子状态引出的匿名转换 和一个父状态,并且您希望定义一个策略,其中 选择。这是计算机实例中的全局设置,并且 默认为 。??transitionConflictPolicy????CHILD??

您可以使用 配置 .它 允许您自动设置 ,其中(如果存在) 包装任何创建的 和 启用分布式模式。以下示例演示如何使用它:??withDistributed()????DistributedStateMachine????StateMachineEnsemble????StateMachine????DistributedStateMachine??

@Configuration@EnableStateMachinepublic class Config18 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withDistributed() .ensemble(stateMachineEnsemble()); } @Bean public StateMachineEnsemble<States, Events> stateMachineEnsemble() throws Exception { // naturally not null but should return ensemble instance return null; }}

有关分布式状态的详细信息,请参阅使用分布式状态。

该接口在内部用于 对状态机的结构进行一些健全性检查。其目的是 尽早快速失败,而不是让常见的配置错误进入 状态机。默认情况下,将自动启用验证程序并使用实现。??StateMachineModelVerifier????DefaultStateMachineModelVerifier??

使用 ,您可以禁用验证程序或设置自定义验证程序,如果 需要。以下示例演示如何执行此操作:??withVerifier()??

@Configuration@EnableStateMachinepublic class Config19 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withVerifier() .enabled(true) .verifier(verifier()); } @Bean public StateMachineModelVerifier<States, Events> verifier() { return new StateMachineModelVerifier<States, Events>() { @Override public void verify(StateMachineModel<States, Events> model) { // throw exception indicating malformed model } }; }}

有关配置模型的详细信息,请参阅状态机配置模型。

和配置方法 分别记录在状态机安全性、监视状态机和使用状态机运行时Persister?中。??withSecurity????withMonitoring????withPersistence??

配置模型

??StateMachineModelFactory??是一个钩子,允许您配置状态机模型 不使用手动配置。本质上,它是第三方 集成以集成到配置模型中。 您可以通过以下方式挂接到配置模型 使用 .以下示例演示如何执行此操作:??StateMachineModelFactory????StateMachineModelConfigurer??

@Configuration@EnableStateMachinepublic static class Config1 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineModelConfigurer<String, String> model) throws Exception { model .withModel() .factory(modelFactory()); } @Bean public StateMachineModelFactory<String, String> modelFactory() { return new CustomStateMachineModelFactory(); }}

以下示例使用 定义两个状态 ( 和 ) 以及介于这两个状态之间的事件 () 国家:??CustomStateMachineModelFactory????S1????S2????E1??

public static class CustomStateMachineModelFactory implements StateMachineModelFactory<String, String> { @Override public StateMachineModel<String, String> build() { ConfigurationData<String, String> configurationData = new ConfigurationData<>(); Collection<StateData<String, String>> stateData = new ArrayList<>(); stateData.add(new StateData<String, String>(“S1”, true)); stateData.add(new StateData<String, String>(“S2”)); StatesData<String, String> statesData = new StatesData<>(stateData); Collection<TransitionData<String, String>> transitionData = new ArrayList<>(); transitionData.add(new TransitionData<String, String>(“S1”, “S2”, “E1”)); TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData); StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<String, String>(configurationData, statesData, transitionsData); return stateMachineModel; } @Override public StateMachineModel<String, String> build(String machineId) { return build(); }}

定义自定义模型通常不是人们想要的, 虽然有可能。但是,这是允许的核心概念 对此配置模型的外部访问。

您可以在Eclipse 建模支持中找到使用此模型工厂集成的示例。您可以找到有关自定义模型集成的更多通用信息 在开发人员文档中。

要记住的事情

定义操作时,防护或来自 配置,记住 Spring 框架的工作原理是值得的 用豆子。在下一个示例中,我们定义了一个普通配置 状态和它们之间的四个过渡。所有过渡 由 或 保护。您必须确保 被创建为真正的 bean,因为它是用 注释的,而不是。??S1????S2????guard1????guard2????guard1????@Bean????guard2??

这意味着该事件将获得条件为 ,并将条件获取为 ,因为这些 来自对这些函数的普通方法调用。??E3????guard2????TRUE????E4????guard2????FALSE??

但是,由于被定义为 ,因此由 弹簧框架。因此,对其方法的其他调用会导致 该实例只有一个实例化。事件将首先获得 带有条件的代理实例,而事件将得到相同的结果 使用 定义方法调用时具有条件的实例。这不是特定于 Spring 状态机的行为。相反,它是 Spring 框架如何与 bean 一起工作。 以下示例显示了此排列的工作原理:??guard1????@Bean????E1????TRUE????E2????TRUE????FALSE??

@Configuration@EnableStateMachinepublic class Config1 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“S1”) .state(“S2”); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source(“S1”).target(“S2”).event(“E1”).guard(guard1(true)) .and() .withExternal() .source(“S1”).target(“S2”).event(“E2”).guard(guard1(false)) .and() .withExternal() .source(“S1”).target(“S2”).event(“E3”).guard(guard2(true)) .and() .withExternal() .source(“S1”).target(“S2”).event(“E4”).guard(guard2(false)); } @Bean public Guard<String, String> guard1(final boolean value) { return new Guard<String, String>() { @Override public boolean evaluate(StateContext<String, String> context) { return value; } }; } public Guard<String, String> guard2(final boolean value) { return new Guard<String, String>() { @Override public boolean evaluate(StateContext<String, String> context) { return value; } }; }}状态机标识

各种类和接口用作变量或 方法中的参数。本节将仔细研究与正常机器操作和实例化的关系。??machineId????machineId??

在运行时,一个真的没有任何大的可操作性 角色,但区分计算机之外 – 例如,当 跟踪日志或进行更深入的调试。有很多不同的 机器实例很快就会让开发人员迷失在翻译中,如果有的话 没有简单的方法来识别这些实例。因此,我们添加了设置 .??machineId????machineId??

用??@EnableStateMachine??

在 Java 配置中设置,然后公开该值 对于日志。该方法也提供相同的功能。下面的示例使用该方法:??machineId????mymachine????machineId????StateMachine.getId()????machineId??

@Overridepublic void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withConfiguration() .machineId(“mymachine”);}

以下日志输出示例显示了 ID:??mymachine??

11:23:54,509 INFO main support.LifecycleObjectSupport [main] -started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine

手动构建器(请参阅通过构建器的状态机)使用相同的配置 接口,这意味着行为是等效的。

用??@EnableStateMachineFactory??

如果您使用该 并使用该 ID 请求新计算机,则可以看到相同的配置, 如以下示例所示:??machineId????StateMachineFactory??

StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);StateMachine<String, String> machine = factory.getStateMachine(“mymachine”);用??StateMachineModelFactory??

在幕后,所有机器配置首先被转换为 因此不需要知道 配置的来源,因为可以从中构建机器 Java 配置、UML 或存储库。如果你想发疯,你也可以使用自定义,这是最低的可能 定义配置的级别。??StateMachineModel????StateMachineFactory????StateMachineModel??

这些都与 ? 还有一个具有以下签名的方法:实现可以选择使用它。??machineId????StateMachineModelFactory????StateMachineModel<S, E> build(String machineId)????StateMachineModelFactory??

??RepositoryStateMachineModelFactory??(请参阅存储库支持)用于支持持久化中的不同配置 通过 Spring 数据存储库接口进行存储。例如,两者都有一个方法(),来构建不同的状态和 转换 .使用 ,if 用作空 或 NULL,默认为存储库配置(在支持持久模型中) 没有已知的计算机 ID。??machineId????StateRepository????TransitionRepository????List<T> findByMachineId(String machineId)????machineId????RepositoryStateMachineModelFactory????machineId??

目前,不区分 不同的计算机 ID,因为 UML 源始终来自同一 文件。这可能会在将来的版本中更改。??UmlStateMachineModelFactory??

国家机器工厂

有些用例需要动态创建状态机 而不是通过在编译时定义静态配置。例如 如果有自定义组件使用自己的状态机 而且这些组件是动态创建的,不可能有 在应用程序启动期间构建的静态状态机。内部 状态机始终通过工厂接口构建。这然后 为您提供以编程方式使用此功能的选项。 状态机工厂的配置与所示完全相同 在本文档中的各种示例中,状态机配置 是硬编码的。

通过适配器出厂

实际上,通过使用工厂创建状态机,因此仅公开 该工厂通过其界面。以下示例使用:??@EnableStateMachine????@EnableStateMachineFactory????@EnableStateMachineFactory??

@Configuration@EnableStateMachineFactorypublic class Config6 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); }}

现在,您已经用于创建工厂 您可以注入它并使用它(按原样)来代替状态机 Bean。 请求新的状态机。以下示例演示如何执行此操作:??@EnableStateMachineFactory??

public class Bean3 { @Autowired StateMachineFactory<States, Events> factory; void method() { StateMachine<States,Events> stateMachine = factory.getStateMachine(); stateMachine.startReactively().subscribe(); }}适配器工厂限制

工厂目前的局限性在于它的所有行动和防护 关联状态机共享同一实例。 这意味着,从你的行动和警惕中,你需要 专门处理同一 Bean 被不同的 状态机。此限制将在 未来版本。

通过生成器的状态机

使用适配器(如上所示)具有其施加的限制 完成春季课程和 应用程序上下文。虽然这是一个非常清晰的模型来配置 状态机,它在编译时限制配置, 这并不总是用户想要做的。如果有要求 要构建更多动态状态机,可以使用简单的构建器模式 以构造类似的实例。通过使用字符串作为状态和 事件,您可以使用此构建器模式构建完全动态的状态 Spring 应用程序上下文之外的机器。以下示例 演示如何执行此操作:??@Configuration??

StateMachine<String, String> buildMachine1() throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureStates() .withStates() .initial(“S1”) .end(“SF”) .states(new HashSet<String>(Arrays.asList(“S1″,”S2″,”S3″,”S4”))); return builder.build();}

构建器在后台使用相同的配置接口 该模型用于适配器类。相同的模型转到 通过构建器的配置转换、状态和通用配置 方法。这意味着你可以用普通的东西,或者你可以通过构建器动态使用。??@Configuration????EnumStateMachineConfigurerAdapter????StateMachineConfigurerAdapter??

目前,、 , 和接口方法不能 链接在一起,这意味着需要单独调用构建器方法。??builder.configureStates()????builder.configureTransitions()????builder.configureConfiguration()??

以下示例使用生成器设置了许多选项:

StateMachine<String, String> buildMachine2() throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureConfiguration() .withConfiguration() .autoStartup(false) .beanFactory(null) .listener(null); return builder.build();}

您需要了解常见配置何时需要 与从构建器实例化的机器一起使用。您可以使用配置器 从 A 返回到安装程序和 。 您也可以使用一个来注册 .如果从构建器返回的实例使用 注册为 Bean,则会自动附加。如果您在 Spring 应用程序上下文之外使用实例, 您必须使用这些方法来设置所需的设施。??withConfiguration()????autoStart????BeanFactory????StateMachineListener????StateMachine????@Bean????BeanFactory??

使用延迟事件

发送事件时,它可能会触发 ,这可能会导致 如果状态机处于触发器处于 评估成功。通常,这可能会导致以下情况: 事件被接受并被丢弃。但是,您可能希望 推迟此事件,直到状态机进入另一种状态。在这种情况下, 您可以接受该事件。换句话说,一个事件 在不方便的时间到达。??EventTrigger??

Spring 状态机提供了一种将事件推迟到以后的机制 加工。每个州都可以有一个延迟事件列表。如果事件 在当前状态的延迟事件发生列表中,事件被保存 (延迟)以供将来处理,直到输入未列出的状态 其延迟事件列表中的事件。当进入这种状态时, 状态机自动调用不再保存的任何事件 延迟,然后使用或丢弃这些事件。这是可能的 使超状态在延迟的事件上定义转换 通过子状态。遵循相同的分层状态机概念,子状态 优先于超状态,事件被推迟,并且 不运行超状态的转换。具有正交区域, 如果一个正交区域推迟事件,另一个区域接受事件,则 接受优先,事件被使用而不是延迟。

事件延迟最明显的用例是当事件导致 转换到特定状态,然后返回状态机 到其原始状态,其中第二个事件应导致相同的事件 过渡。以下示例显示了此情况:

@Configuration@EnableStateMachinestatic class Config5 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“READY”) .state(“DEPLOYPREPARE”, “DEPLOY”) .state(“DEPLOYEXECUTE”, “DEPLOY”); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source(“READY”).target(“DEPLOYPREPARE”) .event(“DEPLOY”) .and() .withExternal() .source(“DEPLOYPREPARE”).target(“DEPLOYEXECUTE”) .and() .withExternal() .source(“DEPLOYEXECUTE”).target(“READY”); }}

在前面的示例中,状态机的状态为 ,这表示该机器是 准备处理事件,使其进入一种状态,其中 实际部署将发生。运行部署操作后,计算机 返回到状态。如果计算机使用同步执行程序,则在一个状态下发送多个事件不会造成任何麻烦, 因为事件发送会在事件调用之间阻塞。但是,如果遗嘱执行人使用 线程,其他事件可能会丢失,因为计算机不再处于 可以处理事件。因此,延迟其中一些事件可以让机器 保护它们。下面的示例演示如何配置此类安排:??READY????DEPLOY????READY????READY??

@Configuration@EnableStateMachinestatic class Config6 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial(“READY”) .state(“DEPLOY”, “DEPLOY”) .state(“DONE”) .and() .withStates() .parent(“DEPLOY”) .initial(“DEPLOYPREPARE”) .state(“DEPLOYPREPARE”, “DONE”) .state(“DEPLOYEXECUTE”); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source(“READY”).target(“DEPLOY”) .event(“DEPLOY”) .and() .withExternal() .source(“DEPLOYPREPARE”).target(“DEPLOYEXECUTE”) .and() .withExternal() .source(“DEPLOYEXECUTE”).target(“READY”) .and() .withExternal() .source(“READY”).target(“DONE”) .event(“DONE”) .and() .withExternal() .source(“DEPLOY”).target(“DONE”) .event(“DONE”); }}

在前面的示例中,状态机使用嵌套状态而不是平面状态 状态模型,因此事件可以直接在子状态中延迟。 它还显示了在 子状态,然后覆盖 如果调度事件时状态机恰好处于某种状态,则表示 AND 状态。在事件未延迟的状态下,此事件将 在超级状态下处理。??DEPLOY????DONE????DEPLOY????DONE????DEPLOYPREPARE????DONE????DEPLOYEXECUTE????DONE??

天才是百分之一的灵感加上百分之久十久的努力

Spring Statemachine状态机的概念

相关文章:

你感兴趣的文章:

标签云: