Spring Statemachine状态机的概念

时间:2022-12-20 14:23:36

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.8

repositories {
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​

@SpringBootApplication
public 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
@EnableStateMachine
public 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​

@Autowired
private StateMachine<States, Events> stateMachine;

@Override
public 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 SI
State change to S1
State 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 描述了状态机错误处理支持。
  • 状态机安全性描述状态机安全性支持。
  • 状态机错误处理描述了状态机拦截器支持。
  • 状态机服务描述状态机服务支持。
  • 持久化状态机描述了状态机持久化支持。
  • Spring Boot Support 描述了 Spring Boot 支持。
  • 监视状态机描述了监视和跟踪支持。
  • 使用分布式状态描述了分布式状态机支持。
  • 测试支持描述了状态机测试支持。
  • Eclipse 建模支持描述了状态机 UML 建模支持。
  • 存储库支持描述了状态机存储库配置支持。

状态机配置

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

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

使用批注​​enable​

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

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

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

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

配置状态

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

@Configuration
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
static 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​

@Override
public 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​

@Autowired
StateMachine<String, String> stateMachine;

void sendEventUsingTimeout() {
stateMachine
.sendEvent(Mono.just(MessageBuilder
.withPayload("E1")
.setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
.build()))
.subscribe();

}

转换操作错误处理

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

@Configuration
@EnableStateMachine
public 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();
}
};
}

}

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

@Override
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
static 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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
@EnableStateMachine
public 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​

@Override
public 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
@EnableStateMachineFactory
public 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
@EnableStateMachine
static 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
@EnableStateMachine
static 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​