前言
状态机的概念很可能比本参考文档的任何读者都更古老,并且肯定比 Java 语言本身更古老。对有限自动机的描述可以追溯到 1943 年,当时 Warren McCulloch 和 Walter Pitts 先生写了一篇关于它的论文。后来,George H. Mealy 在 1955 年提出了状态机概念(称为“Mealy Machine”)。一年后,即 1956 年,Edward F. Moore 发表了另一篇论文,其中描述了所谓的“摩尔机” 。如果您曾经读过有关状态机的任何内容,那么 Mealy 和 Moore 这两个名字应该会在某个时候突然出现。
本参考文档包含以下部分:
简介包含对此参考文档的介绍。
使用Spring Statemachine描述了Spring Statemachine(SSM)的用法。
状态机示例包含更详细的状态机示例。
FAQ 包含常见问题。
附录包含有关所用材料和状态机的一般信息。
介绍
Spring Statemachine (SSM) 是一个允许应用程序开发人员在 Spring 应用程序中使用传统状态机概念的框架。 SSM提供以下功能:
-
适用于简单用例的易于使用的扁平(一级)状态机。
-
分层状态机结构可简化复杂的状态配置。
-
状态机区域提供更复杂的状态配置。
-
触发器、转换、防护和操作的使用。
-
类型安全的配置适配器。
-
状态机事件监听器。
-
Spring IoC 集成将 bean 与状态机关联起来。
在继续之前,我们建议您先阅读附录术语表和状态机速成课程,以大致了解什么是状态机。文档的其余部分希望您熟悉状态机概念。
背景
状态机之所以强大,是因为它们的行为始终保证一致,并且相对容易调试,因为机器启动时操作规则是一成不变的。这个想法是,您的应用程序现在处于并且可能存在于有限数量的状态。然后发生一些事情,使您的应用程序从一种状态进入下一种状态。状态机由触发器驱动,触发器基于事件或计时器。
在应用程序之外设计高级逻辑,然后以各种不同的方式与状态机交互要容易得多。您可以通过发送事件、侦听状态机的操作或请求当前状态来与状态机交互。
传统上,当开发人员意识到代码库开始看起来像一盘意大利面条时,状态机就会添加到现有项目中。意大利面条式代码看起来像是永无休止的 IF、ELSE 和 BREAK 子句的分层结构,当事情开始看起来过于复杂时,编译器可能应该要求开发人员回家。
使用场景
在以下情况下,项目是使用状态机的良好候选者:
-
您可以将应用程序或其结构的一部分表示为状态。
-
您希望将复杂的逻辑拆分为更小的可管理任务。
-
应用程序已经遇到了并发问题(例如)异步发生的事情。
当您执行以下操作时,您已经在尝试实现状态机:
-
使用布尔标志或枚举来模拟情况。
-
具有仅对应用程序生命周期的某些部分有意义的变量。
-
循环遍历 if-else 结构(或者更糟糕的是多个此类结构),检查是否设置了特定标志或枚举,然后进一步排除当标志和枚举的某些组合存在或不存在时要执行的操作。
入门
如果您刚刚开始使用 Spring Statemachine,这部分适合您!在这里,我们回答基本的“ what?
”、“ how?
”和“ why?
”问题。我们首先简单介绍一下 Spring Statemachine。然后,我们构建第一个 Spring Statemachine 应用程序,并讨论一些核心原则。
系统要求
Spring Statemachine 4.0.0 是使用 JDK 8(所有工件都具有 JDK 7 兼容性)和 Spring Framework 6.0.14 构建和测试的。它在其核心系统内不需要 Spring 框架之外的任何其他依赖项。
其他可选部分(例如使用分布式状态)依赖于 Zookeeper,而状态机示例依赖于 spring-shell
和 spring-boot
,这将其他依赖项拉到框架本身之外。此外,可选的安全和数据访问功能依赖于 Spring Security 和 Spring Data 模块。
模块
下表描述了可用于 Spring Statemachine 的模块。
模块 | 描述 |
---|---|
spring-statemachine-core
|
Spring Statemachine的核心系统。
| |
spring-statemachine-recipes-common
|
不需要核心框架之外的依赖项的常见配方。
| |
spring-statemachine-kryo
|
Kryo
Spring Statemachine 的序列化器。
| |
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 Cloud Cluster 已被 Spring Integration 取代。
| |
spring-statemachine-uml
|
使用 Eclipse Papyrus 进行 UI UML 建模的支持模块。
| |
spring-statemachine-autoconfigure
|
Spring Boot 的支持模块。
| |
spring-statemachine-bom
|
物料清单 pom.
| |
spring-statemachine-starter
|
Spring Boot 启动器。
|
使用 Gradle
以下列表显示了通过在 https://start.spring.io 选择各种设置创建的典型 build.gradle
文件:
buildscript {
ext {
springBootVersion = '3.1.6'
}
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 = '4.0.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 替换为您要使用的版本。 |
使用正常的项目结构,您可以使用以下命令构建该项目:
预期的 Spring Boot 打包的 fat jar 将为 build/libs/demo-0.0.1-SNAPSHOT.jar
。
您不需要`libs-milestone`和 libs-snapshot 存储库来进行生产开发。 |
使用Maven
以下示例显示了一个典型的 pom.xml
文件,该文件是通过在 https://start.spring.io 上选择各种选项来创建的:
<?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>3.1.6</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>4.0.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 替换为您要使用的版本。 |
使用正常的项目结构,您可以使用以下命令构建该项目:
预期的 Spring Boot 打包的 fat-jar 将为 target/demo-0.0.1-SNAPSHOT.jar
。
您不需要 libs-milestone 和 libs-snapshot 存储库来进行生产开发。 |
您可以首先创建一个实现 CommandLineRunner
的简单 Spring Boot Application
类。以下示例展示了如何执行此操作:
@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
和 autowire 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 Boot 输出。但是,您还应该找到以下几行:
State change to SI
State change to S1
State change to S2
这些线表明您构建的机器正在从一种状态转移到另一种状态,正如它应该的那样。