状态机服务
状态机服务是更高级别的实现,旨在 提供更多用户级功能,简化正常运行时间 操作。目前只有一个服务接口 () 存在。StateMachineService
用StateMachineService
StateMachineService是一个用于处理正在运行的计算机的接口 并具有“获取”和“释放”机器的简单方法。它有 一个默认实现,名为 。DefaultStateMachineService
持久化状态机
传统上,状态机的实例按原样使用 正在运行的程序。您可以使用以下方法实现更动态的行为 动态构建器和工厂,允许状态机 按需实例化。构建状态机实例是一个 操作相对较重。因此,如果您需要(例如)处理 使用状态机对数据库中的任意状态更改,需要 找到更好,更快的方法来做到这一点。
持久功能允许您保存状态机的状态 到外部存储库中,然后根据 序列化状态。例如,如果您有一个数据库表保存 订单,用状态更新订单状态太昂贵了 机器,如果需要为每个更改构建一个新实例。 持久功能允许您重置状态机状态,而无需 实例化新的状态机实例。
有一个配方(请参阅持久)和一个示例 (请参阅持久),提供以下详细信息 持久状态。
虽然您可以使用 来构建自定义持久性功能,但它有一个概念问题。当侦听器 通知状态更改,状态更改已经发生。如果 侦听器中的自定义持久方法无法更新序列化 外部存储库中的状态、状态机中的状态和状态 然后,外部存储库处于不一致状态。StateMachineListener
您可以改用状态机拦截器来尝试保存 在状态期间将状态序列化到外部存储中 状态机内的更改。如果此拦截器回调失败, 您可以停止状态更改尝试,而不是以 不一致状态,然后可以手动处理此错误。有关如何使用拦截器的信息,请参阅使用状态机拦截器。
用StateMachineContext
不能使用普通 java 持久化 序列化,因为对象图太丰富并且包含太多 对其他 Spring 上下文类的依赖。 是状态机的运行时表示形式,可用于 将现有计算机还原到由特定对象表示的状态。StateMachineStateMachineContextStateMachineContext
StateMachineContext包含两种不同的信息包含方式 对于子上下文。这些通常在机器包含以下情况时使用 正交区域。首先,上下文可以具有子上下文的列表 如果它们存在,可以按原样使用。其次,你可以 包括原始上下文子项时使用的引用列表 不到位。这些子引用确实是唯一的方法 保留正在运行多个并行区域的计算机 独立地。
数据多重持久示例显示 如何保留并行区域。
用StateMachinePersister
构建然后还原状态机 从它一直有点“黑魔法”如果完成 手动地。该界面旨在缓解这些 通过提供和方法进行操作。默认 此接口的实现为 。StateMachineContextStateMachinePersisterpersistrestoreDefaultStateMachinePersister
我们可以通过以下方式展示如何使用 来自测试的片段。我们首先创建两个类似的配置 ( 和 ) 表示状态机。请注意,我们可以构建不同的 以其他方式进行此演示的机器,但以这种方式 适用于这种情况。以下示例配置两个状态机:StateMachinePersistermachine1machine2
@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 转换为状态。然后我们可以 坚持它并恢复 .以下示例演示如何执行此操作:machine1S2E1machine2
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 保存到 中。StateMachinePersistRedisStateMachineContextRepositorykryoStateMachineContextRedis
对于 ,我们有一个与 Redis 相关的实现,它采用 a 并用作其上下文对象。StateMachinePersisterRedisStateMachinePersisterStateMachinePersistString
有关详细用法,请参阅事件服务示例。
RedisStateMachineContextRepository需要一个才能工作。我们建议使用 for 它,如前面的示例所示。RedisConnectionFactoryJedisConnectionFactory
用StateMachineRuntimePersister
StateMachineRuntimePersister是一个简单的扩展,它添加了一个接口级方法来与之关联。然后,此拦截器 需要在状态更改期间保留计算机,而无需 停止和启动计算机。StateMachinePersistStateMachineInterceptor
目前,此接口有 支持的 Spring 数据存储库。这些实现是 、 和。JpaPersistingStateMachineInterceptorMongoDbPersistingStateMachineInterceptorRedisPersistingStateMachineInterceptor
有关详细用法,请参阅数据保留示例。
弹簧启动支持
自动配置模块 () 包含所有 与 Spring 引导集成的逻辑,它提供了以下功能 自动配置和执行器。您所需要的只是拥有这台弹簧状态机 库作为引导应用程序的一部分。spring-statemachine-autoconfigure
监视和跟踪
BootStateMachineMonitor自动创建并关联 状态机。 是一个与 Spring 启动和端点集成的自定义实现 通过自定义.(可选)您可以禁用此自动配置 通过将键设置为 .监视示例演示如何使用此自动配置。BootStateMachineMonitorStateMachineMonitorMeterRegistryStateMachineTraceRepositoryspring.statemachine.monitor.enabledfalse
存储库配置
如果从类路径中找到所需的类, Spring 数据存储库 实体类扫描会自动配置 存储库支持。
当前支持的配置包括 、 和 。您可以分别使用 和 属性禁用存储库自动配置。JPARedisMongoDBspring.statemachine.data.jpa.repositories.enabledspring.statemachine.data.redis.repositories.enabledspring.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@EnableStateMachinepublic 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(); }}有关详细用法,请参阅监视示例。
使用分布式状态
分布式状态可能是 弹簧状态机。究竟什么是分布式状态?一个状态 在单个状态机内自然非常简单理解, 但是,当需要引入共享分布式状态时 通过状态机,事情变得有点复杂。
分布式状态功能仍然是预览功能,不是 但在此特定版本中被认为是稳定的。我们期待这个 功能成熟到首次正式发布。
有关通用配置支持的信息,请参阅配置通用设置。有关实际使用示例,请参阅 动物园管理员示例。
分布式状态机通过包装实际实例的类实现 的 . 拦截 与实例通信并使用 通过接口处理的分布式状态抽象。根据实际实施情况, 还可以使用该接口序列化 ,其中包含足以重置 的信息。DistributedStateMachineStateMachineDistributedStateMachineStateMachineStateMachineEnsembleStateMachinePersistStateMachineContextStateMachine
虽然分布式状态机是通过抽象实现的, 当前仅存在一个实现。它基于动物园管理员。
以下示例演示如何配置基于 Zookeeper 的分布式状态 机器':
@Configuration@EnableStateMachinepublic 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本身需要两个强制性设置, 的实例和 .客户端是 ,路径是实例中树的根。curatorClientbasePathCuratorFrameworkZookeeper
或者,您可以设置 ,如果融合中不存在任何成员,则默认为 并清除现有数据。您可以设置 如果要保留分布式状态,则将其保留在 应用程序重新启动。cleanStateTRUEFALSE
或者,您可以设置 (默认值 到 ) 以保留状态更改的历史记录。这个的价值 设置必须是 2 的幂。 通常是一个很好的默认值 价值。如果特定状态机落后于 日志的大小,它被置于错误状态并与 合奏,表明它已经失去了它的历史和完全重建的能力 已同步状态。logSize3232
测试支持
我们还添加了一组实用程序类来简化状态测试 计算机实例。这些在框架本身中使用,但也 对最终用户非常有用。
StateMachineTestPlanBuilder构建一个 , 它有一个方法(称为 )。该方法运行计划。 包含一个流畅的构建器 API,可让您添加 计划的步骤。在这些步骤中,您可以发送事件并检查 各种条件,例如状态更改、转换和扩展状态 变量。StateMachineTestPlantest()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();}在下面的测试计划中,我们有两个步骤。首先,我们检查初始 状态 () 确实已设置。其次,我们发送一个事件()并期望 发生一种状态更改,并期望计算机最终处于 . 以下清单显示了测试计划:SIE1S1
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-machineStateMachine Diagram
我们要创建一台具有两种状态(和)的机器,其中 是初始状态。然后,我们需要创建事件来执行转换 从 到 。在Papyrus中,机器看起来像什么东西。 以下示例:S1S2S1E1S1S2
在后台,原始 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:id="_AMP3IP8fEeW45bORGB4c_A" name="RootElement"> <packagedElement xmi:type="uml:StateMachine" xmi:id="_AMRFQP8fEeW45bORGB4c_A" name="StateMachine"> <region xmi:type="uml:Region" xmi:id="_AMRsUP8fEeW45bORGB4c_A" name="Region1"> <transition xmi:type="uml:Transition" xmi:id="_chgcgP8fEeW45bORGB4c_A" source="_EZrg4P8fEeW45bORGB4c_A" target="_FAvg4P8fEeW45bORGB4c_A"> <trigger xmi:type="uml:Trigger" xmi:id="_hs5jUP8fEeW45bORGB4c_A" event="_NeH84P8fEeW45bORGB4c_A"/> </transition> <transition xmi:type="uml:Transition" xmi:id="_egLIoP8fEeW45bORGB4c_A" source="_Fg0IEP8fEeW45bORGB4c_A" target="_EZrg4P8fEeW45bORGB4c_A"/> <subvertex xmi:type="uml:State" xmi:id="_EZrg4P8fEeW45bORGB4c_A" name="S1"/> <subvertex xmi:type="uml:State" xmi:id="_FAvg4P8fEeW45bORGB4c_A" name="S2"/> <subvertex xmi:type="uml:Pseudostate" xmi:id="_Fg0IEP8fEeW45bORGB4c_A"/> </region> </packagedElement> <packagedElement xmi:type="uml:Signal" xmi:id="_L01D0P8fEeW45bORGB4c_A" name="E1"/> <packagedElement xmi:type="uml:SignalEvent" xmi:id="_NeH84P8fEeW45bORGB4c_A" name="SignalEventE1" signal="_L01D0P8fEeW45bORGB4c_A"/></uml:Model>打开已定义为 UML 的现有模型时,有三个 文件:、 和 。如果未在 Eclipse的会话,它不了解如何打开实际状态 图表。这是 Papyrus 插件中的一个已知问题,并且有一个简单的 解决方法。在 Papyrus 透视中,您可以看到一个模型浏览器 您的模型。双击图状态机图,其中 指示 Eclipse 在其适当的 Papyrus 中打开此特定模型 建模插件。.di.notation.uml
用UmlStateMachineModelFactory
在项目中放置 UML 文件后,可以将其导入到 使用 进行配置,其中 与模型相关联。 是一家知道如何 处理 Eclipse Papyrus_generated UML 结构。源 UML 文件可以 要么作为 Spring 给出,要么作为普通位置字符串给出。 下面的示例演示如何创建 的实例:StateMachineModelConfigurerStateMachineModelFactoryUmlStateMachineModelFactoryResourceUmlStateMachineModelFactory
@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 UmlStateMachineModelFactory("classpath:org/springframework/statemachine/uml/docs/simple-machine.uml"); }}像往常一样,Spring 状态机与警卫和 操作,定义为 bean。那些需要连接到UML 通过其内部建模结构。以下部分显示 如何在 UML 定义中定义自定义 Bean 引用。 请注意,也可以手动注册特定方法 没有将它们定义为豆子。
如果创建为 Bean,则会自动连接它以查找已注册的操作和 警卫。您也可以手动定义 ,然后用于查找这些 组件。工厂还具有 registerAction 和 registerGuard 方法,您可以使用它们来注册这些组件。欲了解更多信息 关于这一点,请参阅使用状态机器组件解析程序。UmlStateMachineModelFactoryResourceLoaderStateMachineComponentResolver
UML 模型在涉及实现时相对松散,例如 弹簧状态机本身。Spring 状态机留下如何实现很多功能和 功能直至实际实施。以下部分转到 通过 Spring 状态机如何实现基于 UML 模型 Eclipse Papyrus插件。
用StateMachineComponentResolver
下一个示例演示如何使用 a ,分别寄存 和 函数。请注意,这些组件 不是作为 Bean 创建的。下面的清单显示了该示例:UmlStateMachineModelFactoryStateMachineComponentResolvermyActionmyGuard
@Configuration@EnableStateMachinepublic 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; } }; }}创建模型
我们首先创建一个空的状态机模型,如下图所示:
可以从创建新模型并为其命名开始,如下图所示:
然后你需要选择状态机图,如下所示:
你最终得到一个空的状态机。
在前面的图像中,您应该已经创建了一个名为 的示例。 您应该以三个文件结束:、 和 。然后,您可以在任何其他文件中使用这些文件 日食实例。此外,您可以导入到 弹簧状态机。modelmodel.dimodel.notationmodel.umlmodel.uml
定义状态
状态标识符来自图中的组件名称。 您的计算机中必须有一个初始状态,您可以通过添加 一个根元素,然后绘制到您自己的初始状态的过渡, 如下图所示:
在上图中,我们添加了一个根元素和一个初始状态 ()。然后我们画了一个过渡 在这两者之间,以指示这是初始状态。S1S1
在上图中,我们添加了第二个状态 (),并在 S1 和 S2(表示我们有两种状态)。S2
定义事件
要将事件与转换关联,您需要创建一个信号 (,在本例中)。为此,请选择“根元素”→“新子→信号”。 下图显示了结果:E1
然后,您需要使用新信号 . 为此,请选择 RootElement → New Child→ SignalEvent。 下图显示了结果:E1
现在您已经定义了 ,您可以使用它来关联 具有过渡的触发器。有关此内容的详细信息,请参阅定义转换。SignalEvent
推迟事件
您可以推迟事件以在更合适的时间处理它们。在 UML,这是从状态本身完成的。选择任何州,创建一个 在可延迟触发器下新建触发器,然后选择 SignalEvent 匹配要延迟的信号。
定义过渡
您可以通过在 源和目标状态。在前面的图片中,我们有状态和 两者之间的匿名转换。我们希望将事件与该转换相关联。我们选择过渡,创造新的 触发器,并为此定义 SignalEventE1,如下图所示:S1S2E1
这为您提供了类似于下图所示的排列方式:
如果省略转换的信号事件,它将变为 匿名转换。
定义计时器
转换也可以基于定时事件发生。弹簧状态机 支持两种类型的计时器,一种在 背景和在状态为时延迟触发一次的背景 进入。
若要将新的 TimeEvent 子项添加到模型资源管理器,请将 When 修改为 定义为文本整数的表达式。它的值(以毫秒为单位)成为计时器。 离开相对为 false,以使计时器连续触发。
要定义一个在进入状态时触发的基于定时的事件,该过程恰好 与前面所述相同,但将“相对”设置为 true。下图 显示结果:
然后,用户可以选择这些定时事件之一,而不是 特定转换的信号事件。
定义选择
通过将一个传入过渡绘制到 CHOICE 状态并绘制从它到目标的多个传出转换 国家。我们的配置模型允许您定义 一个 if/elseif/else 结构。但是,对于UML,我们需要使用 用于传出过渡的个人警卫。StateConfigurer
必须确保为过渡定义的防护不重叠,以便: 无论发生什么,在任何给定情况下,只有一个守卫的计算结果为 TRUE。 时间。这为选择分支提供了精确和可预测的结果 评估。此外,我们建议在没有防护装置的情况下留下一个过渡 以便保证至少一个过渡路径。 下图显示了使用三个分支进行选择的结果:
Junction 的工作方式类似,只是它允许多个传入 转换。因此,与选择相比,它的行为纯粹是 学术。选择传出转换的实际逻辑完全相同。
定义交汇点
请参阅定义选项。
定义入口和退出点
您可以使用入口点和出口点创建受控的进入和退出 与具有次州的国家。在下面的状态图中,事件和进入和退出状态具有正常状态的行为,其中正常状态行为通过进入初始状态而发生。E1E2S2S21
使用事件将计算机带入入口点,然后 导致在任何时候都不会激活初始状态。 类似地,带有事件的退出点控制特定的出口 进入状态,而正常的退出行为将采取 机器进入状态。在状态中,您可以选择 事件,并使计算机进入状态或 , 分别。下图显示了结果:E3ENTRYS22S21EXITE4S4S2S3S22E4E2S3S4
如果状态定义为子机引用,并且需要使用入口和退出点, 您必须在外部定义一个 ConnectionPointReference,使用 其进入和退出引用设置为指向正确的入口或退出点 在子机器引用中。只有在那之后,才有可能 定位从外部正确链接到内部的过渡 子计算机引用。使用 ConnectionPointReference,您可能需要 从“高级属性”→“→ UML →中查找这些设置 进入/退出。UML 规范允许您定义多个入口和出口。然而 对于状态机,只允许一个状态机。
定义历史状态
在处理历史状态时,有三个不同的概念在起作用。 UML定义了深层历史和浅层历史。默认历史记录 当历史状态尚不为人所知时,状态就会发挥作用。这些是 在以下各节中表示。
浅历史
在下图中,选择了“浅层历史记录”,并在其中定义了过渡:
深厚的历史
深度历史记录用于具有其他深层嵌套状态的状态, 从而有机会保存整个嵌套状态结构。 下图显示了使用深层历史记录的定义:
默认历史记录
如果转换在历史记录上终止,则在以下情况下 该州在达到其 最终状态,可以选择强制 使用默认值过渡到特定子状态 历史机制。为此,您必须定义一个转换 进入此默认状态。这是从 到 的过渡。SHS22
在下图中,如果状态具有 从未活跃过,因为它的历史从未被记录过。如果状态处于活动状态,则选择任一 或。S22S2S2S20S21
定义分叉和联接
叉子和连接在纸莎草纸中都表示为条形图。如图所示 在下图中,您需要绘制一个从 into 状态到具有正交区域的传出过渡。 然后是相反的,其中 联接状态是从传入转换中一起收集的。FORKS2JOIN
定义操作
您可以使用行为关联 swtate 进入和退出操作。 有关此内容的更多信息,请参阅定义 Bean 引用。
使用初始操作
定义了初始操作(如配置操作中所示) 在 UML 中,通过在转换中添加一个从初始状态引导的操作 标记到实际状态。然后,当状态 机器已启动。
定义守卫
您可以通过先添加约束,然后定义 它的规范为不透明表达式,其工作方式相同 作为定义 Bean 引用。
定义 Bean 引用
当您需要在任何 UML 效果中进行 Bean 引用时, 操作或保护,您可以使用 或 执行此操作,其中定义的语言需要 be 和语言体 msut 有一个 Bean 引用 ID。FunctionBehaviorOpaqueBehaviorbean
定义 SpEL 引用
当您需要使用 SpEL 表达式而不是 Bean 引用时 任何 UML 效果、操作或保护,都可以通过使用 或 来实现,其中定义的语言需要 是,语言主体必须是 SpEL 表达式。FunctionBehaviorOpaqueBehaviorspel
使用子机器引用
通常,当您使用子状态时,您会将它们绘制到状态中 图表本身。图表可能变得过于复杂和大而无法 follow,所以我们也支持将子状态定义为状态机 参考。
若要创建子计算机引用,必须首先创建一个新关系图并为其命名 (例如,子状态机图)。下图显示了要使用的菜单选项:
为新图表提供所需的设计。 下图显示了一个简单的设计作为示例:
从要链接的状态(在本例中为 m 状态)中,单击该字段并选择链接的计算机(在我们的示例中为 )。S2SubmachineSubStateMachine
最后,在下图中,您可以看到状态链接到 子状态。S2SubStateMachine
使用计算机导入
还可以使用导入功能,其中 uml 文件可以引用其他模型。
可以使用其他资源或位置 以定义参照的模型文件。UmlStateMachineModelFactory
@Configuration@EnableStateMachinepublic 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 数据存储库抽象进行。
我们创建了一个特殊的实现 叫。它可以使用底座 存储库接口(、 和 )和基本实体 接口 (、、 和 )。StateMachineModelFactoryRepositoryStateMachineModelFactoryStateRepositoryTransitionRepositoryActionRepositoryGuardRepositoryRepositoryStateRepositoryTransitionRepositoryActionRepositoryGuard
由于实体和存储库在 Spring 数据中的工作方式, 从用户的角度来看,读取访问可以完全抽象化 在 中完成。没有必要 了解存储库使用的实际映射实体类。 写入存储库始终依赖于使用真实的 特定于存储库的实体类。从机器配置点 当然,我们不需要知道这些,这意味着我们不需要知道 实际实现是JPA,Redis还是其他任何东西 Spring Data支持。使用与存储库相关的实际存储库 当您手动尝试编写新的实体类时,实体类开始发挥作用 状态或转换为受支持的存储库。RepositoryStateMachineModelFactory
的实体类 和 具有一个字段,该字段可供您使用,可用于 区分配置 — 例如,如果构建了机器 通过。RepositoryStateRepositoryTransitionmachineIdStateMachineFactory
实际实现将在后面的部分中记录。 下图是存储库的 UML 等效状态图 配置。
图1.简单机器
图2.简单子机
图3.展示机
太平绅士
JPA 的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。JpaStateRepositoryJpaTransitionRepositoryJpaActionRepositoryJpaGuardRepositoryJpaRepositoryStateJpaRepositoryTransitionJpaRepositoryActionJpaRepositoryGuard
不幸的是,版本“1.2.8”不得不对JPA的实体进行更改 关于使用的表名的模型。以前,生成的表名 始终具有前缀 ,派生自实体类 名字。因为这会导致数据库强加的中断性问题 对数据库对象长度的限制,所有实体类都有 强制表名的特定定义。例如,现在是“状态”——以此类推,其他 NTITY类。JPA_REPOSITORY_JPA_REPOSITORY_STATE
显示了手动更新 JPA 状态和转换的通用方法 在以下示例中(相当于 SimpleMachine 中显示的计算机):
@AutowiredStateRepository<JpaRepositoryState> stateRepository;@AutowiredTransitionRepository<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 中显示的计算机。
@AutowiredStateRepository<JpaRepositoryState> stateRepository;@AutowiredTransitionRepository<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);}首先,您必须访问所有存储库。 以下示例演示如何执行此操作:
@AutowiredStateRepository<JpaRepositoryState> stateRepository;@AutowiredTransitionRepository<JpaRepositoryTransition> transitionRepository;@AutowiredActionRepository<JpaRepositoryAction> actionRepository;@AutowiredGuardRepository<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 实例的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。RedisStateRepositoryRedisTransitionRepositoryRedisActionRepositoryRedisGuardRepositoryRedisRepositoryStateRedisRepositoryTransitionRedisRepositoryActionRedisRepositoryGuard
下一个示例显示了手动更新 Redis 的状态和转换的通用方法。 这相当于SimpleMachine中显示的机器。
@AutowiredStateRepository<RedisRepositoryState> stateRepository;@AutowiredTransitionRepository<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 中显示的机器:
@AutowiredStateRepository<RedisRepositoryState> stateRepository;@AutowiredTransitionRepository<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 实例的实际存储库实现是 、 、 和 ,它们由 实体类 、 和 分别。MongoDbStateRepositoryMongoDbTransitionRepositoryMongoDbActionRepositoryMongoDbGuardRepositoryMongoDbRepositoryStateMongoDbRepositoryTransitionMongoDbRepositoryActionMongoDbRepositoryGuard
下一个示例显示了手动更新 MongoDB 的状态和转换的通用方法。 这相当于 SimpleMachine 中显示的机器。
@AutowiredStateRepository<MongoDbRepositoryState> stateRepository;@AutowiredTransitionRepository<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 中显示的计算机。
@AutowiredStateRepository<MongoDbRepositoryState> stateRepository;@AutowiredTransitionRepository<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);}存储库持久性
除了将机器配置(如存储库配置中所示)存储在外部存储库中外,您还可以 将计算机保存到存储库中。
接口是一个中央接入点, 与机器持久互,并由实体类提供支持。StateMachineRepositoryRepositoryStateMachine
太平绅士
JPA 的实际存储库实现是 ,它由实体类提供支持。JpaStateMachineRepositoryJpaRepositoryStateMachine
以下示例显示了为 JPA 持久化计算机的通用方法:
@AutowiredStateMachineRepository<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 的实际存储库实现是 ,它由实体类提供支持。RedisStateMachineRepositoryRedisRepositoryStateMachine
以下示例显示了为 Redis 保留计算机的通用方法:
@AutowiredStateMachineRepository<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的实际存储库实现是,它由实体类支持。MongoDbStateMachineRepositoryMongoDbRepositoryStateMachine
以下示例显示了为 MongoDB 持久化计算机的通用方法:
@AutowiredStateMachineRepository<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_idTASK_1TASK_212
执行 可能会导致错误。特别是如果一个复杂的 涉及任务的DAG,您希望有一种方法来处理 任务执行错误,然后有办法继续执行 无需执行已成功执行的任务。也 如果可以处理一些执行错误,那就太好了 自然而然。作为最后的回退,如果无法处理错误 状态机自动进入用户可以处理的状态 手动错误。Runnable
TasksHandler包含用于配置处理程序实例的生成器方法 并遵循简单的构建器模式。您可以使用此构建器来 注册任务和实例并定义钩子。RunnableTasksListenerStateMachinePersist
现在我们可以采用一个简单的运行简单睡眠,如下所示 示例显示:Runnable
private Runnable sleepRunnable() { return new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } } };}前面的示例是本章中所有示例的基础。
要执行多个任务,您可以注册任务和 从 执行方法,如以下示例所示:sleepRunnablerunTasks()TasksHandler
TasksHandler handler = TasksHandler.builder() .task("1", sleepRunnable()) .task("2", sleepRunnable()) .task("3", sleepRunnable()) .build();handler.runTasks();要侦听任务执行发生的情况,您可以注册一个实例 a 带 .这个食谱 如果您不想,请提供适配器 实现完整接口。侦听器提供各种钩子 侦听任务执行事件。下面的示例演示类的定义:TasksListenerTasksHandlerTasksListenerAdapterMyTasksListener
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();当发生错误并且运行这些任务的状态机进入某种状态时,您可以调用处理程序方法来 重置保持状态机扩展状态的任务的当前状态 变量。然后,可以使用处理程序方法来 指示状态机从状态转换回状态,您可以在其中再次运行任务。 以下示例演示如何执行此操作:ERRORfixCurrentProblemscontinueFromErrorERRORREADY
TasksHandler handler = TasksHandler.builder() .task("1", sleepRunnable()) .task("2", sleepRunnable()) .task("3", sleepRunnable()) .build(); handler.runTasks(); handler.fixCurrentProblems(); handler.continueFromError();