Spring Statemachine状态机的概念(三)

时间:2022-12-20 11:56:42

Spring Statemachine状态机的概念(三)

状态机服务

状态机服务是更高级别的实现,旨在 提供更多用户级功能,简化正常运行时间 操作。目前只有一个服务接口 () 存在。​​StateMachineService​

用​​StateMachineService​

​StateMachineService​​是一个用于处理正在运行的计算机的接口 并具有“获取”和“释放”机器的简单方法。它有 一个默认实现,名为 。​​DefaultStateMachineService​

持久化状态机

传统上,状态机的实例按原样使用 正在运行的程序。您可以使用以下方法实现更动态的行为 动态构建器和工厂,允许状态机 按需实例化。构建状态机实例是一个 操作相对较重。因此,如果您需要(例如)处理 使用状态机对数据库中的任意状态更改,需要 找到更好,更快的方法来做到这一点。

持久功能允许您保存状态机的状态 到外部存储库中,然后根据 序列化状态。例如,如果您有一个数据库表保存 订单,用状态更新订单状态太昂贵了 机器,如果需要为每个更改构建一个新实例。 持久功能允许您重置状态机状态,而无需 实例化新的状态机实例。

有一个配方(请参阅持久)​和一个示例 (请参阅持久),提供以下详细信息 持久状态。

虽然您可以使用 来构建自定义持久性功能,但它有一个概念问题。当侦听器 通知状态更改,状态更改已经发生。如果 侦听器中的自定义持久方法无法更新序列化 外部存储库中的状态、状态机中的状态和状态 然后,外部存储库处于不一致状态。​​StateMachineListener​

您可以改用状态机拦截器来尝试保存 在状态期间将状态序列化到外部存储中 状态机内的更改。如果此拦截器回调失败, 您可以停止状态更改尝试,而不是以 不一致状态,然后可以手动处理此错误。有关如何使用拦截器的信息,请参阅使用状态机拦截器。

用​​StateMachineContext​

不能使用普通 java 持久化 序列化,因为对象图太丰富并且包含太多 对其他 Spring 上下文类的依赖。 是状态机的运行时表示形式,可用于 将现有计算机还原到由特定对象表示的状态。​​StateMachine​​​​StateMachineContext​​​​StateMachineContext​

​StateMachineContext​​包含两种不同的信息包含方式 对于子上下文。这些通常在机器包含以下情况时使用 正交区域。首先,上下文可以具有子上下文的列表 如果它们存在,可以按原样使用。其次,你可以 包括原始上下文子项时使用的引用列表 不到位。这些子引用确实是唯一的方法 保留正在运行多个并行区域的计算机 独立地。

数据多重持久示例显示 如何保留并行区域。

用​​StateMachinePersister​

构建然后还原状态机 从它一直有点“黑魔法”如果完成 手动地。该界面旨在缓解这些 通过提供和方法进行操作。默认 此接口的实现为 。​​StateMachineContext​​​​StateMachinePersister​​​​persist​​​​restore​​​​DefaultStateMachinePersister​

我们可以通过以下方式展示如何使用 来自测试的片段。我们首先创建两个类似的配置 ( 和 ) 表示状态机。请注意,我们可以构建不同的 以其他方式进行此演示的机器,但以这种方式 适用于这种情况。以下示例配置两个状态机:​​StateMachinePersister​​​​machine1​​​​machine2​

@Configuration
@EnableStateMachine(name = "machine1")
static class Config1 extends Config {
}

@Configuration
@EnableStateMachine(name = "machine2")
static class Config2 extends Config {
}

static class Config extends StateMachineConfigurerAdapter<String, String> {

@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S1")
.state("S2");
}

@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1");
}
}

当我们使用对象时,我们可以创建一个内存 实现。​​StateMachinePersist​

此内存中示例仅用于演示目的。真实 应用程序,您应该使用真正的持久存储实现。

下面的清单显示了如何使用内存中示例:

static class InMemoryStateMachinePersist implements StateMachinePersist<String, String, String> {

private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();

@Override
public void write(StateMachineContext<String, String> context, String contextObj) throws Exception {
contexts.put(contextObj, context);
}

@Override
public StateMachineContext<String, String> read(String contextObj) throws Exception {
return contexts.get(contextObj);
}
}

在实例化了两台不同的机器之后,我们可以通过 event 转换为状态。然后我们可以 坚持它并恢复 .以下示例演示如何执行此操作:​​machine1​​​​S2​​​​E1​​​​machine2​

InMemoryStateMachinePersist stateMachinePersist = new InMemoryStateMachinePersist();
StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
stateMachine1.startReactively().block();

stateMachine1
.sendEvent(Mono.just(MessageBuilder
.withPayload("E1").build()))
.blockLast();
assertThat(stateMachine1.getState().getIds()).containsExactly("S2");

persister.persist(stateMachine1, "myid");
persister.restore(stateMachine2, "myid");
assertThat(stateMachine2.getState().getIds()).containsExactly("S2");

使用红地

​RepositoryStateMachinePersist​​(实现 )支持将状态机持久化到 Redis 中。 具体的实现是 ,它使用序列化来 将 A 保存到 中。​​StateMachinePersist​​​​RedisStateMachineContextRepository​​​​kryo​​​​StateMachineContext​​​​Redis​

对于 ,我们有一个与 Redis 相关的实现,它采用 a 并用作其上下文对象。​​StateMachinePersister​​​​RedisStateMachinePersister​​​​StateMachinePersist​​​​String​

有关详细用法,请参阅事件服务示例。

​RedisStateMachineContextRepository​​需要一个才能工作。我们建议使用 for 它,如前面的示例所示。​​RedisConnectionFactory​​​​JedisConnectionFactory​

用​​StateMachineRuntimePersister​

​StateMachineRuntimePersister​​是一个简单的扩展,它添加了一个接口级方法来与之关联。然后,此拦截器 需要在状态更改期间保留计算机,而无需 停止和启动计算机。​​StateMachinePersist​​​​StateMachineInterceptor​

目前,此接口有 支持的 Spring 数据存储库。这些实现是 、 和。​​JpaPersistingStateMachineInterceptor​​​​MongoDbPersistingStateMachineInterceptor​​​​RedisPersistingStateMachineInterceptor​

有关详细用法,请参阅数据保留示例。

弹簧启动支持

自动配置模块 () 包含所有 与 Spring 引导集成的逻辑,它提供了以下功能 自动配置和执行器。您所需要的只是拥有这台弹簧状态机 库作为引导应用程序的一部分。​​spring-statemachine-autoconfigure​

监视和跟踪

​BootStateMachineMonitor​​自动创建并关联 状态机。 是一个与 Spring 启动和端点集成的自定义实现 通过自定义.(可选)您可以禁用此自动配置 通过将键设置为 .监视示例演示如何使用此自动配置。​​BootStateMachineMonitor​​​​StateMachineMonitor​​​​MeterRegistry​​​​StateMachineTraceRepository​​​​spring.statemachine.monitor.enabled​​​​false​

存储库配置

如果从类路径中找到所需的类, Spring 数据存储库 实体类扫描会自动配置 存储库支持。

当前支持的配置包括 、 和 。您可以分别使用 和 属性禁用存储库自动配置。​​JPA​​​​Redis​​​​MongoDB​​​​spring.statemachine.data.jpa.repositories.enabled​​​​spring.statemachine.data.redis.repositories.enabled​​​​spring.statemachine.data.mongo.repositories.enabled​

监控状态机

您可以使用 获取有关 执行转换和操作所需的时间持续时间。以下列表 演示如何实现此接口。​​StateMachineMonitor​

public class TestStateMachineMonitor extends AbstractStateMachineMonitor<String, String> {

@Override
public void transition(StateMachine<String, String> stateMachine, Transition<String, String> transition,
long duration) {
}

@Override
public void action(StateMachine<String, String> stateMachine,
Function<StateContext<String, String>, Mono<Void>> action, long duration) {
}
}

获得实现后,可以将其添加到 通过配置的状态机,如以下示例所示:​​StateMachineMonitor​

@Configuration
@EnableStateMachine
public class Config1 extends StateMachineConfigurerAdapter<String, String> {

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withMonitoring()
.monitor(stateMachineMonitor());
}

@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");
}

@Bean
public StateMachineMonitor<String, String> stateMachineMonitor() {
return new TestStateMachineMonitor();
}
}

有关详细用法,请参阅监视示例。

使用分布式状态

分布式状态可能是 弹簧状态机。究竟什么是分布式状态?一个状态 在单个状态机内自然非常简单理解, 但是,当需要引入共享分布式状态时 通过状态机,事情变得有点复杂。

分布式状态功能仍然是预览功能,不是 但在此特定版本中被认为是稳定的。我们期待这个 功能成熟到首次正式发布。

有关通用配置支持的信息,请参阅配置通用设置。有关实际使用示例,请参阅 动物园管理员示例。

分布式状态机通过包装实际实例的类实现 的 . 拦截 与实例通信并使用 通过接口处理的分布式状态抽象。根据实际实施情况, 还可以使用该接口序列化 ,其中包含足以重置 的信息。​​DistributedStateMachine​​​​StateMachine​​​​DistributedStateMachine​​​​StateMachine​​​​StateMachineEnsemble​​​​StateMachinePersist​​​​StateMachineContext​​​​StateMachine​

虽然分布式状态机是通过抽象实现的, 当前仅存在一个实现。它基于动物园管理员。

以下示例演示如何配置基于 Zookeeper 的分布式状态 机器':

@Configuration
@EnableStateMachine
public class Config
extends StateMachineConfigurerAdapter<String, String> {

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withDistributed()
.ensemble(stateMachineEnsemble())
.and()
.withConfiguration()
.autoStartup(true);
}

@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
// config states
}

@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
// config transitions
}

@Bean
public StateMachineEnsemble<String, String> stateMachineEnsemble()
throws Exception {
return new ZookeeperStateMachineEnsemble<String, String>(curatorClient(), "/zkpath");
}

@Bean
public CuratorFramework curatorClient()
throws Exception {
CuratorFramework client = CuratorFrameworkFactory
.builder()
.defaultData(new byte[0])
.connectString("localhost:2181").build();
client.start();
return client;
}

}

您可以找到基于 Zookeeker 的分布式的当前技术文档 附录中的状态机。

用​​ZookeeperStateMachineEnsemble​

​ZookeeperStateMachineEnsemble​​本身需要两个强制性设置, 的实例和 .客户端是 ,路径是实例中树的根。​​curatorClient​​​​basePath​​​​CuratorFramework​​​​Zookeeper​

或者,您可以设置 ,如果融合中不存在任何成员,则默认为 并清除现有数据。您可以设置 如果要保留分布式状态,则将其保留在 应用程序重新启动。​​cleanState​​​​TRUE​​​​FALSE​

或者,您可以设置 (默认值 到 ) 以保留状态更改的历史记录。这个的价值 设置必须是 2 的幂。 通常是一个很好的默认值 价值。如果特定状态机落后于 日志的大小,它被置于错误状态并与 合奏,表明它已经失去了它的历史和完全重建的能力 已同步状态。​​logSize​​​​32​​​​32​

测试支持

我们还添加了一组实用程序类来简化状态测试 计算机实例。这些在框架本身中使用,但也 对最终用户非常有用。

​StateMachineTestPlanBuilder​​构建一个 , 它有一个方法(称为 )。该方法运行计划。 包含一个流畅的构建器 API,可让您添加 计划的步骤。在这些步骤中,您可以发送事件并检查 各种条件,例如状态更改、转换和扩展状态 变量。​​StateMachineTestPlan​​​​test()​​​​StateMachineTestPlanBuilder​

以下示例用于构建状态机:​​StateMachineBuilder​

private StateMachine<String, String> buildMachine() throws Exception {
StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();

builder.configureConfiguration()
.withConfiguration()
.autoStartup(true);

builder.configureStates()
.withStates()
.initial("SI")
.state("S1");

builder.configureTransitions()
.withExternal()
.source("SI").target("S1")
.event("E1")
.action(c -> {
c.getExtendedState().getVariables().put("key1", "value1");
});

return builder.build();
}

在下面的测试计划中,我们有两个步骤。首先,我们检查初始 状态 () 确实已设置。其次,我们发送一个事件()并期望 发生一种状态更改,并期望计算机最终处于 . 以下清单显示了测试计划:​​SI​​​​E1​​​​S1​

StateMachine<String, String> machine = buildMachine();
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.defaultAwaitTime(2)
.stateMachine(machine)
.step()
.expectStates("SI")
.and()
.step()
.sendEvent("E1")
.expectStateChanged(1)
.expectStates("S1")
.expectVariable("key1")
.expectVariable("key1", "value1")
.expectVariableWith(hasKey("key1"))
.expectVariableWith(hasValue("value1"))
.expectVariableWith(hasEntry("key1", "value1"))
.expectVariableWith(not(hasKey("key2")))
.and()
.build();
plan.test();

这些实用程序也用于测试分布式 状态机功能。请注意,可以将多台计算机添加到计划中。 如果添加多台计算机,还可以选择 将事件发送到特定计算机、随机计算机或所有计算机。

前面的测试示例使用以下 Hamcrest 导入:

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.hamcrest.collection.IsMapContaining.hasValue;

import org.junit.jupiter.api.Test;

import static org.hamcrest.collection.IsMapContaining.hasEntry;

预期结果的所有可能选项都记录在 Javadoc for StateMachineTestPlanStepBuilder 中。

日食建模支持

支持使用 UI 建模定义状态机配置 通过Eclipse Papyrus框架。

从 Eclipse 向导中,您可以使用 UML 图创建新的纸莎草模型 语言。在此示例中,它被命名为 .然后你 可以选择从各种图表类型中进行选择,并且必须选择 .​​simple-machine​​​​StateMachine Diagram​

我们要创建一台具有两种状态(和)的机器,其中 是初始状态。然后,我们需要创建事件来执行转换 从 到 。在Papyrus中,机器看起来像什么东西。 以下示例:​​S1​​​​S2​​​​S1​​​​E1​​​​S1​​​​S2​

Spring Statemachine状态机的概念(三)

在后台,原始 UML 文件类似于以下示例:

<?xml version="1.0" encoding="UTF-8"?>
<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi: name="RootElement">
<packagedElement xmi:type="uml:StateMachine" xmi: name="StateMachine">
<region xmi:type="uml:Region" xmi: name="Region1">
<transition xmi:type="uml:Transition" xmi: source="_EZrg4P8fEeW45bORGB4c_A" target="_FAvg4P8fEeW45bORGB4c_A">
<trigger xmi:type="uml:Trigger" xmi: event="_NeH84P8fEeW45bORGB4c_A"/>
</transition>
<transition xmi:type="uml:Transition" xmi: source="_Fg0IEP8fEeW45bORGB4c_A" target="_EZrg4P8fEeW45bORGB4c_A"/>
<subvertex xmi:type="uml:State" xmi: name="S1"/>
<subvertex xmi:type="uml:State" xmi: name="S2"/>
<subvertex xmi:type="uml:Pseudostate" xmi:/>
</region>
</packagedElement>
<packagedElement xmi:type="uml:Signal" xmi: name="E1"/>
<packagedElement xmi:type="uml:SignalEvent" xmi: name="SignalEventE1" signal="_L01D0P8fEeW45bORGB4c_A"/>
</uml:Model>

打开已定义为 UML 的现有模型时,有三个 文件:、 和 。如果未在 Eclipse的会话,它不了解如何打开实际状态 图表。这是 Papyrus 插件中的一个已知问题,并且有一个简单的 解决方法。在 Papyrus 透视中,您可以看到一个模型浏览器 您的模型。双击图状态机图,其中 指示 Eclipse 在其适当的 Papyrus 中打开此特定模型 建模插件。​​.di​​​​.notation​​​​.uml​

用​​UmlStateMachineModelFactory​

在项目中放置 UML 文件后,可以将其导入到 使用 进行配置,其中 与模型相关联。 是一家知道如何 处理 Eclipse Papyrus_generated UML 结构。源 UML 文件可以 要么作为 Spring 给出,要么作为普通位置字符串给出。 下面的示例演示如何创建 的实例:​​StateMachineModelConfigurer​​​​StateMachineModelFactory​​​​UmlStateMachineModelFactory​​​​Resource​​​​UmlStateMachineModelFactory​

@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 UmlStateMachineModelFactory("classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
}
}

像往常一样,Spring 状态机与警卫和 操作,定义为 bean。那些需要连接到UML 通过其内部建模结构。以下部分显示 如何在 UML 定义中定义自定义 Bean 引用。 请注意,也可以手动注册特定方法 没有将它们定义为豆子。

如果创建为 Bean,则会自动连接它以查找已注册的操作和 警卫。您也可以手动定义 ,然后用于查找这些 组件。工厂还具有 registerAction 和 registerGuard 方法,您可以使用它们来注册这些组件。欲了解更多信息 关于这一点,请参阅使用状态机器组件解析程序。​​UmlStateMachineModelFactory​​​​ResourceLoader​​​​StateMachineComponentResolver​

UML 模型在涉及实现时相对松散,例如 弹簧状态机本身。Spring 状态机留下如何实现很多功能和 功能直至实际实施。以下部分转到 通过 Spring 状态机如何实现基于 UML 模型 Eclipse Papyrus插件。

用​​StateMachineComponentResolver​

下一个示例演示如何使用 a ,分别寄存 和 函数。请注意,这些组件 不是作为 Bean 创建的。下面的清单显示了该示例:​​UmlStateMachineModelFactory​​​​StateMachineComponentResolver​​​​myAction​​​​myGuard​

@Configuration
@EnableStateMachine
public static class Config2 extends StateMachineConfigurerAdapter<String, String> {

@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}

@Bean
public StateMachineModelFactory<String, String> modelFactory() {
UmlStateMachineModelFactory factory = new UmlStateMachineModelFactory(
"classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
factory.setStateMachineComponentResolver(stateMachineComponentResolver());
return factory;
}

@Bean
public StateMachineComponentResolver<String, String> stateMachineComponentResolver() {
DefaultStateMachineComponentResolver<String, String> resolver = new DefaultStateMachineComponentResolver<>();
resolver.registerAction("myAction", myAction());
resolver.registerGuard("myGuard", myGuard());
return resolver;
}

public Action<String, String> myAction() {
return new Action<String, String>() {

@Override
public void execute(StateContext<String, String> context) {
}
};
}

public Guard<String, String> myGuard() {
return new Guard<String, String>() {

@Override
public boolean evaluate(StateContext<String, String> context) {
return false;
}
};
}
}

创建模型

我们首先创建一个空的状态机模型,如下图所示:

Spring Statemachine状态机的概念(三)

可以从创建新模型并为其命名开始,如下图所示:

Spring Statemachine状态机的概念(三)

然后你需要选择状态机图,如下所示:

Spring Statemachine状态机的概念(三)

你最终得到一个空的状态机。

在前面的图像中,您应该已经创建了一个名为 的示例。 您应该以三个文件结束:、 和 。然后,您可以在任何其他文件中使用这些文件 日食实例。此外,您可以导入到 弹簧状态机。​​model​​​​model.di​​​​model.notation​​​​model.uml​​​​model.uml​

定义状态

状态标识符来自图中的组件名称。 您的计算机中必须有一个初始状态,您可以通过添加 一个根元素,然后绘制到您自己的初始状态的过渡, 如下图所示:

Spring Statemachine状态机的概念(三)

在上图中,我们添加了一个根元素和一个初始状态 ()。然后我们画了一个过渡 在这两者之间,以指示这是初始状态。​​S1​​​​S1​

Spring Statemachine状态机的概念(三)

在上图中,我们添加了第二个状态 (),并在 S1 和 S2(表示我们有两种状态)。​​S2​

定义事件

要将事件与转换关联,您需要创建一个信号 (,在本例中)。为此,请选择“根元素”→“新子→信号”。 下图显示了结果:​​E1​

Spring Statemachine状态机的概念(三)

然后,您需要使用新信号 . 为此,请选择 RootElement → New Child→ SignalEvent。 下图显示了结果:​​E1​

Spring Statemachine状态机的概念(三)

现在您已经定义了 ,您可以使用它来关联 具有过渡的触发器。有关此内容的详细信息,请参阅定义转换。​​SignalEvent​

推迟事件

您可以推迟事件以在更合适的时间处理它们。在 UML,这是从状态本身完成的。选择任何州,创建一个 在可延迟触发器下新建触发器,然后选择 SignalEvent 匹配要延迟的信号。

定义过渡

您可以通过在 源和目标状态。在前面的图片中,我们有状态和 两者之间的匿名转换。我们希望将事件与该转换相关联。我们选择过渡,创造新的 触发器,并为此定义 SignalEventE1,如下图所示:​​S1​​​​S2​​​​E1​

Spring Statemachine状态机的概念(三)

这为您提供了类似于下图所示的排列方式:

Spring Statemachine状态机的概念(三)

如果省略转换的信号事件,它将变为 匿名转换。

定义计时器

转换也可以基于定时事件发生。弹簧状态机 支持两种类型的计时器,一种在 背景和在状态为时延迟触发一次的背景 进入。

若要将新的 TimeEvent 子项添加到模型资源管理器,请将 When 修改为 定义为文本整数的表达式。它的值(以毫秒为单位)成为计时器。 离开相对为 false,以使计时器连续触发。

Spring Statemachine状态机的概念(三)

要定义一个在进入状态时触发的基于定时的事件,该过程恰好 与前面所述相同,但将“相对”设置为 true。下图 显示结果:

Spring Statemachine状态机的概念(三)

然后,用户可以选择这些定时事件之一,而不是 特定转换的信号事件。

定义选择

通过将一个传入过渡绘制到 CHOICE 状态并绘制从它到目标的多个传出转换 国家。我们的配置模型允许您定义 一个 if/elseif/else 结构。但是,对于UML,我们需要使用 用于传出过渡的个人警卫。​​StateConfigurer​

必须确保为过渡定义的防护不重叠,以便: 无论发生什么,在任何给定情况下,只有一个守卫的计算结果为 TRUE。 时间。这为选择分支提供了精确和可预测的结果 评估。此外,我们建议在没有防护装置的情况下留下一个过渡 以便保证至少一个过渡路径。 下图显示了使用三个分支进行选择的结果:

Spring Statemachine状态机的概念(三)

Junction 的工作方式类似,只是它允许多个传入 转换。因此,与选择相比,它的行为纯粹是 学术。选择传出转换的实际逻辑完全相同。

定义交汇点

请参阅定义选项。

定义入口和退出点

您可以使用入口点和出口点创建受控的进入和退出 与具有次州的国家。在下面的状态图中,事件和进入和退出状态具有正常状态的行为,其中正常状态行为通过进入初始状态而发生。​​E1​​​​E2​​​​S2​​​​S21​

使用事件将计算机带入入口点,然后 导致在任何时候都不会激活初始状态。 类似地,带有事件的退出点控制特定的出口 进入状态,而正常的退出行为将采取 机器进入状态。在状态中,您可以选择 事件,并使计算机进入状态或 , 分别。下图显示了结果:​​E3​​​​ENTRY​​​​S22​​​​S21​​​​EXIT​​​​E4​​​​S4​​​​S2​​​​S3​​​​S22​​​​E4​​​​E2​​​​S3​​​​S4​

Spring Statemachine状态机的概念(三)

如果状态定义为子机引用,并且需要使用入口和退出点, 您必须在外部定义一个 ConnectionPointReference,使用 其进入和退出引用设置为指向正确的入口或退出点 在子机器引用中。只有在那之后,才有可能 定位从外部正确链接到内部的过渡 子计算机引用。使用 ConnectionPointReference,您可能需要 从“高级属性”→“→ UML →中查找这些设置 进入/退出。UML 规范允许您定义多个入口和出口。然而 对于状态机,只允许一个状态机。

定义历史状态

在处理历史状态时,有三个不同的概念在起作用。 UML定义了深层历史和浅层历史。默认历史记录 当历史状态尚不为人所知时,状态就会发挥作用。这些是 在以下各节中表示。

浅历史

在下图中,选择了“浅层历史记录”,并在其中定义了过渡:

Spring Statemachine状态机的概念(三)

深厚的历史

深度历史记录用于具有其他深层嵌套状态的状态, 从而有机会保存整个嵌套状态结构。 下图显示了使用深层历史记录的定义:

Spring Statemachine状态机的概念(三)

默认历史记录

如果转换在历史记录上终止,则在以下情况下 该州在达到其 最终状态,可以选择强制 使用默认值过渡到特定子状态 历史机制。为此,您必须定义一个转换 进入此默认状态。这是从 到 的过渡。​​SH​​​​S22​

在下图中,如果状态具有 从未活跃过,因为它的历史从未被记录过。如果状态处于活动状态,则选择任一 或。​​S22​​​​S2​​​​S2​​​​S20​​​​S21​

Spring Statemachine状态机的概念(三)

定义分叉和联接

叉子和连接在纸莎草纸中都表示为条形图。如图所示 在下图中,您需要绘制一个从 into 状态到具有正交区域的传出过渡。 然后是相反的,其中 联接状态是从传入转换中一起收集的。​​FORK​​​​S2​​​​JOIN​

Spring Statemachine状态机的概念(三)

定义操作

您可以使用行为关联 swtate 进入和退出操作。 有关此内容的更多信息,请参阅定义 Bean 引用。

使用初始操作

定义了初始操作(如配置操作中所示) 在 UML 中,通过在转换中添加一个从初始状态引导的操作 标记到实际状态。然后,当状态 机器已启动。

定义守卫

您可以通过先添加约束,然后定义 它的规范为不透明表达式,其工作方式相同 作为定义 Bean 引用。

定义 Bean 引用

当您需要在任何 UML 效果中进行 Bean 引用时, 操作或保护,您可以使用 或 执行此操作,其中定义的语言需要 be 和语言体 msut 有一个 Bean 引用 ID。​​FunctionBehavior​​​​OpaqueBehavior​​​​bean​

定义 SpEL 引用

当您需要使用 SpEL 表达式而不是 Bean 引用时 任何 UML 效果、操作或保护,都可以通过使用 或 来实现,其中定义的语言需要 是,语言主体必须是 SpEL 表达式。​​FunctionBehavior​​​​OpaqueBehavior​​​​spel​

使用子机器引用

通常,当您使用子状态时,您会将它们绘制到状态中 图表本身。图表可能变得过于复杂和大而无法 follow,所以我们也支持将子状态定义为状态机 参考。

若要创建子计算机引用,必须首先创建一个新关系图并为其命名 (例如,子状态机图)。下图显示了要使用的菜单选项:

Spring Statemachine状态机的概念(三)

为新图表提供所需的设计。 下图显示了一个简单的设计作为示例:

Spring Statemachine状态机的概念(三)

从要链接的状态(在本例中为 m 状态)中,单击该字段并选择链接的计算机(在我们的示例中为 )。​​S2​​​​Submachine​​​​SubStateMachine​

Spring Statemachine状态机的概念(三)

最后,在下图中,您可以看到状态链接到 子状态。​​S2​​​​SubStateMachine​

Spring Statemachine状态机的概念(三)

使用计算机导入

还可以使用导入功能,其中 uml 文件可以引用其他模型。

Spring Statemachine状态机的概念(三)

可以使用其他资源或位置 以定义参照的模型文件。​​UmlStateMachineModelFactory​

@Configuration
@EnableStateMachine
public static class Config3 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 UmlStateMachineModelFactory(
"classpath:org/springframework/statemachine/uml/import-main/import-main.uml",
new String[] { "classpath:org/springframework/statemachine/uml/import-sub/import-sub.uml" });
}
}

uml 模型中文件之间的链接需要相对为 否则,当从 类路径到临时目录,以便 Eclipse 解析类可以 阅读这些。

存储库支持

本节包含与使用“Spring 数据”相关的文档 Spring 状态机中的存储库。

存储库配置

您可以将计算机配置保留在外部 存储,可以从中按需加载,而不是创建静态存储 使用 Java 配置或基于 UML 的配置进行配置。这 集成通过 Spring 数据存储库抽象进行。

我们创建了一个特殊的实现 叫。它可以使用底座 存储库接口(、 和 )和基本实体 接口 (、、 和 )。​​StateMachineModelFactory​​​​RepositoryStateMachineModelFactory​​​​StateRepository​​​​TransitionRepository​​​​ActionRepository​​​​GuardRepository​​​​RepositoryState​​​​RepositoryTransition​​​​RepositoryAction​​​​RepositoryGuard​

由于实体和存储库在 Spring 数据中的工作方式, 从用户的角度来看,读取访问可以完全抽象化 在 中完成。没有必要 了解存储库使用的实际映射实体类。 写入存储库始终依赖于使用真实的 特定于存储库的实体类。从机器配置点 当然,我们不需要知道这些,这意味着我们不需要知道 实际实现是JPA,Redis还是其他任何东西 Spring Data支持。使用与存储库相关的实际存储库 当您手动尝试编写新的实体类时,实体类开始发挥作用 状态或转换为受支持的存储库。​​RepositoryStateMachineModelFactory​

的实体类 和 具有一个字段,该字段可供您使用,可用于 区分配置 — 例如,如果构建了机器 通过。​​RepositoryState​​​​RepositoryTransition​​​​machineId​​​​StateMachineFactory​

实际实现将在后面的部分中记录。 下图是存储库的 UML 等效状态图 配置。

Spring Statemachine状态机的概念(三)

图1.简单机器

Spring Statemachine状态机的概念(三)

图2.简单子机

Spring Statemachine状态机的概念(三)

图3.展示机

太平绅士

JPA 的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。​​JpaStateRepository​​​​JpaTransitionRepository​​​​JpaActionRepository​​​​JpaGuardRepository​​​​JpaRepositoryState​​​​JpaRepositoryTransition​​​​JpaRepositoryAction​​​​JpaRepositoryGuard​

不幸的是,版本“1.2.8”不得不对JPA的实体进行更改 关于使用的表名的模型。以前,生成的表名 始终具有前缀 ,派生自实体类 名字。因为这会导致数据库强加的中断性问题 对数据库对象长度的限制,所有实体类都有 强制表名的特定定义。例如,现在是“状态”——以此类推,其他 NTITY类。​​JPA_REPOSITORY_​​​​JPA_REPOSITORY_STATE​

显示了手动更新 JPA 状态和转换的通用方法 在以下示例中(相当于 SimpleMachine 中显示的计算机):

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);

JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS2, stateS3, "E2");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}

以下示例也等效于 SimpleSubMachine 中显示的计算机。

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS22 = new JpaRepositoryState("S22");
stateS22.setParentState(stateS2);

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
stateRepository.save(stateS21);
stateRepository.save(stateS22);

JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS21, stateS22, "E2");
JpaRepositoryTransition transitionS21ToS22 = new JpaRepositoryTransition(stateS2, stateS3, "E3");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
transitionRepository.save(transitionS21ToS22);
}

首先,您必须访问所有存储库。 以下示例演示如何执行此操作:

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;

@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;

其次,你创造行动和守卫。 以下示例演示如何执行此操作:

JpaRepositoryGuard foo0Guard = new JpaRepositoryGuard();
foo0Guard.setName("foo0Guard");

JpaRepositoryGuard foo1Guard = new JpaRepositoryGuard();
foo1Guard.setName("foo1Guard");

JpaRepositoryAction fooAction = new JpaRepositoryAction();
fooAction.setName("fooAction");

guardRepository.save(foo0Guard);
guardRepository.save(foo1Guard);
actionRepository.save(fooAction);

第三,必须创建状态。 以下示例演示如何执行此操作:

JpaRepositoryState stateS0 = new JpaRepositoryState("S0", true);
stateS0.setInitialAction(fooAction);
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
stateS1.setParentState(stateS0);
JpaRepositoryState stateS11 = new JpaRepositoryState("S11", true);
stateS11.setParentState(stateS1);
JpaRepositoryState stateS12 = new JpaRepositoryState("S12");
stateS12.setParentState(stateS1);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
stateS2.setParentState(stateS0);
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS211 = new JpaRepositoryState("S211", true);
stateS211.setParentState(stateS21);
JpaRepositoryState stateS212 = new JpaRepositoryState("S212");
stateS212.setParentState(stateS21);

stateRepository.save(stateS0);
stateRepository.save(stateS1);
stateRepository.save(stateS11);
stateRepository.save(stateS12);
stateRepository.save(stateS2);
stateRepository.save(stateS21);
stateRepository.save(stateS211);
stateRepository.save(stateS212);

第四,也是最后一点,您必须创建过渡。 以下示例演示如何执行此操作:

JpaRepositoryTransition transitionS1ToS1 = new JpaRepositoryTransition(stateS1, stateS1, "A");
transitionS1ToS1.setGuard(foo1Guard);

JpaRepositoryTransition transitionS1ToS11 = new JpaRepositoryTransition(stateS1, stateS11, "B");
JpaRepositoryTransition transitionS21ToS211 = new JpaRepositoryTransition(stateS21, stateS211, "B");
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "C");
JpaRepositoryTransition transitionS1ToS0 = new JpaRepositoryTransition(stateS1, stateS0, "D");
JpaRepositoryTransition transitionS211ToS21 = new JpaRepositoryTransition(stateS211, stateS21, "D");
JpaRepositoryTransition transitionS0ToS211 = new JpaRepositoryTransition(stateS0, stateS211, "E");
JpaRepositoryTransition transitionS1ToS211 = new JpaRepositoryTransition(stateS1, stateS211, "F");
JpaRepositoryTransition transitionS2ToS21 = new JpaRepositoryTransition(stateS2, stateS21, "F");
JpaRepositoryTransition transitionS11ToS211 = new JpaRepositoryTransition(stateS11, stateS211, "G");

JpaRepositoryTransition transitionS0 = new JpaRepositoryTransition(stateS0, stateS0, "H");
transitionS0.setKind(TransitionKind.INTERNAL);
transitionS0.setGuard(foo0Guard);
transitionS0.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS1 = new JpaRepositoryTransition(stateS1, stateS1, "H");
transitionS1.setKind(TransitionKind.INTERNAL);

JpaRepositoryTransition transitionS2 = new JpaRepositoryTransition(stateS2, stateS2, "H");
transitionS2.setKind(TransitionKind.INTERNAL);
transitionS2.setGuard(foo1Guard);
transitionS2.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS11ToS12 = new JpaRepositoryTransition(stateS11, stateS12, "I");
JpaRepositoryTransition transitionS12ToS212 = new JpaRepositoryTransition(stateS12, stateS212, "I");
JpaRepositoryTransition transitionS211ToS12 = new JpaRepositoryTransition(stateS211, stateS12, "I");

JpaRepositoryTransition transitionS11 = new JpaRepositoryTransition(stateS11, stateS11, "J");
JpaRepositoryTransition transitionS2ToS1 = new JpaRepositoryTransition(stateS2, stateS1, "K");

transitionRepository.save(transitionS1ToS1);
transitionRepository.save(transitionS1ToS11);
transitionRepository.save(transitionS21ToS211);
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS1ToS0);
transitionRepository.save(transitionS211ToS21);
transitionRepository.save(transitionS0ToS211);
transitionRepository.save(transitionS1ToS211);
transitionRepository.save(transitionS2ToS21);
transitionRepository.save(transitionS11ToS211);
transitionRepository.save(transitionS0);
transitionRepository.save(transitionS1);
transitionRepository.save(transitionS2);
transitionRepository.save(transitionS11ToS12);
transitionRepository.save(transitionS12ToS212);
transitionRepository.save(transitionS211ToS12);
transitionRepository.save(transitionS11);
transitionRepository.save(transitionS2ToS1);

您可以在此处找到完整的示例。此示例还演示了如何 从具有以下特征的现有 JSON 文件预填充存储库 实体类的定义。

雷迪斯

Redis 实例的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。​​RedisStateRepository​​​​RedisTransitionRepository​​​​RedisActionRepository​​​​RedisGuardRepository​​​​RedisRepositoryState​​​​RedisRepositoryTransition​​​​RedisRepositoryAction​​​​RedisRepositoryGuard​

下一个示例显示了手动更新 Redis 的状态和转换的通用方法。 这相当于SimpleMachine中显示的机器。

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);


RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}

以下示例等效于 SimpleSubMachine 中显示的机器:

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);


RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}

蒙戈数据库

MongoDB 实例的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。​​MongoDbStateRepository​​​​MongoDbTransitionRepository​​​​MongoDbActionRepository​​​​MongoDbGuardRepository​​​​MongoDbRepositoryState​​​​MongoDbRepositoryTransition​​​​MongoDbRepositoryAction​​​​MongoDbRepositoryGuard​

下一个示例显示了手动更新 MongoDB 的状态和转换的通用方法。 这相当于 SimpleMachine 中显示的机器。

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);

MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS2, stateS3, "E2");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}

以下示例等效于 SimpleSubMachine 中显示的计算机。

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

MongoDbRepositoryState stateS21 = new MongoDbRepositoryState("S21", true);
stateS21.setParentState(stateS2);
MongoDbRepositoryState stateS22 = new MongoDbRepositoryState("S22");
stateS22.setParentState(stateS2);

stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
stateRepository.save(stateS21);
stateRepository.save(stateS22);

MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS21, stateS22, "E2");
MongoDbRepositoryTransition transitionS21ToS22 = new MongoDbRepositoryTransition(stateS2, stateS3, "E3");

transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
transitionRepository.save(transitionS21ToS22);
}

存储库持久性

除了将机器配置(如存储库配置中所示)存储在外部存储库中外,您还可以 将计算机保存到存储库中。

接口是一个*接入点, 与机器持久互,并由实体类提供支持。​​StateMachineRepository​​​​RepositoryStateMachine​

太平绅士

JPA 的实际存储库实现是 ,它由实体类提供支持。​​JpaStateMachineRepository​​​​JpaRepositoryStateMachine​

以下示例显示了为 JPA 持久化计算机的通用方法:

@Autowired
StateMachineRepository<JpaRepositoryStateMachine> stateMachineRepository;

void persist() {

JpaRepositoryStateMachine machine = new JpaRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });

stateMachineRepository.save(machine);
}

雷迪斯

Redis 的实际存储库实现是 ,它由实体类提供支持。​​RedisStateMachineRepository​​​​RedisRepositoryStateMachine​

以下示例显示了为 Redis 保留计算机的通用方法:

@Autowired
StateMachineRepository<RedisRepositoryStateMachine> stateMachineRepository;

void persist() {

RedisRepositoryStateMachine machine = new RedisRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });

stateMachineRepository.save(machine);
}

蒙戈数据库

MongoDB的实际存储库实现是,它由实体类支持。​​MongoDbStateMachineRepository​​​​MongoDbRepositoryStateMachine​

以下示例显示了为 MongoDB 持久化计算机的通用方法:

@Autowired
StateMachineRepository<MongoDbRepositoryStateMachine> stateMachineRepository;

void persist() {

MongoDbRepositoryStateMachine machine = new MongoDbRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });

stateMachineRepository.save(machine);
}

食谱

本章包含现有内置状态的文档 机器配方。

Spring Statemachine是一个基础框架。也就是说,它没有太多 更高级别的功能或 Spring 框架之外的许多依赖项。 因此,正确使用状态机可能很困难。为了提供帮助, 我们创建了一组解决常见用例的配方模块。

究竟什么是食谱?状态机配方是解决常见问题的模块 用例。从本质上讲,状态机配方既是我们尝试的一个例子 让您轻松重用和扩展。

食谱是为春天做出外部贡献的好方法 状态机项目。如果您还没有准备好为 框架核心本身,一个自定义和通用的配方是一个很好的方式 与其他用户共享功能。

坚持

持久配方是一个简单的实用程序,可让您使用单个状态 用于保留和更新任意项状态的计算机实例 存储库。

配方的主类是 ,它做出了三个假设:​​PersistStateMachineHandler​

  • 需要使用的实例 带有 .请注意,状态和事件是必需的 的类型为 。StateMachine<String, String>PersistStateMachineHandlerString
  • ​PersistStateChangeListener​​需要向处理程序注册 对持久请求做出反应。
  • 该方法用于协调状态更改。handleEventWithState

您可以在 Persist 中找到演示如何使用此配方的示例。

任务

任务配方是一个概念,用于运行使用 状态机。此食谱是根据引入的想法开发的 在任务示例中。​​Runnable​

下图显示了状态机的一般概念。在此状态图中, 下面的所有内容都显示了单个的通用概念 任务被执行。因为这个食谱可以让你注册一个深 任务的分层 DAG(意味着真实的状态图将是一个深度 子州和地区的嵌套集合),我们不需要 更精确。​​TASKS​

例如,如果只有两个已注册的任务,则以下状态图表 替换为 和 时是正确的(假设 注册的任务 ID 为 和 )。​​TASK_id​​​​TASK_1​​​​TASK_2​​​​1​​​​2​

Spring Statemachine状态机的概念(三)

执行 可能会导致错误。特别是如果一个复杂的 涉及任务的DAG,您希望有一种方法来处理 任务执行错误,然后有办法继续执行 无需执行已成功执行的任务。也 如果可以处理一些执行错误,那就太好了 自然而然。作为最后的回退,如果无法处理错误 状态机自动进入用户可以处理的状态 手动错误。​​Runnable​

​TasksHandler​​包含用于配置处理程序实例的生成器方法 并遵循简单的构建器模式。您可以使用此构建器来 注册任务和实例并定义钩子。​​Runnable​​​​TasksListener​​​​StateMachinePersist​

现在我们可以采用一个简单的运行简单睡眠,如下所示 示例显示:​​Runnable​

private Runnable sleepRunnable() {
return new Runnable() {

@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
};
}

前面的示例是本章中所有示例的基础。

要执行多个任务,您可以注册任务和 从 执行方法,如以下示例所示:​​sleepRunnable​​​​runTasks()​​​​TasksHandler​

TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("2", sleepRunnable())
.task("3", sleepRunnable())
.build();

handler.runTasks();

要侦听任务执行发生的情况,您可以注册一个实例 a 带 .这个食谱 如果您不想,请提供适配器 实现完整接口。侦听器提供各种钩子 侦听任务执行事件。下面的示例演示类的定义:​​TasksListener​​​​TasksHandler​​​​TasksListenerAdapter​​​​MyTasksListener​

private class MyTasksListener extends TasksListenerAdapter {

@Override
public void onTasksStarted() {
}

@Override
public void onTasksContinue() {
}

@Override
public void onTaskPreExecute(Object id) {
}

@Override
public void onTaskPostExecute(Object id) {
}

@Override
public void onTaskFailed(Object id, Exception exception) {
}

@Override
public void onTaskSuccess(Object id) {
}

@Override
public void onTasksSuccess() {
}

@Override
public void onTasksError() {
}

@Override
public void onTasksAutomaticFix(TasksHandler handler, StateContext<String, String> context) {
}
}

您可以使用构建器注册侦听器,也可以直接使用 注册侦听器,如以下示例所示:​​TasksHandler​

MyTasksListener listener1 = new MyTasksListener();
MyTasksListener listener2 = new MyTasksListener();

TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("2", sleepRunnable())
.task("3", sleepRunnable())
.listener(listener1)
.build();

handler.addTasksListener(listener2);
handler.removeTasksListener(listener2);

handler.runTasks();

每项任务 需要具有唯一标识符,并且(可选)任务可以是 定义为子任务。实际上,这将创建任务的 DAG。 下面的示例演示如何创建任务的深层嵌套 DAG:

TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("1", "12", sleepRunnable())
.task("1", "13", sleepRunnable())
.task("2", sleepRunnable())
.task("2", "22", sleepRunnable())
.task("2", "23", sleepRunnable())
.task("3", sleepRunnable())
.task("3", "32", sleepRunnable())
.task("3", "33", sleepRunnable())
.build();

handler.runTasks();

当发生错误并且运行这些任务的状态机进入某种状态时,您可以调用处理程序方法来 重置保持状态机扩展状态的任务的当前状态 变量。然后,可以使用处理程序方法来 指示状态机从状态转换回状态,您可以在其中再次运行任务。 以下示例演示如何执行此操作:​​ERROR​​​​fixCurrentProblems​​​​continueFromError​​​​ERROR​​​​READY​

TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("2", sleepRunnable())
.task("3", sleepRunnable())
.build();

handler.runTasks();
handler.fixCurrentProblems();
handler.continueFromError();