使用 Spring Statemachine

参考文档的这一部分介绍了核心功能 Spring Statemachine 提供给任何基于 Spring 的应用程序。spring-doc.cn

它包括以下主题:spring-doc.cn

12. 状态机配置

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

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

12.1. 使用 Commentsenable

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

当您需要配置来创建 的实例。通常,类扩展适配器 ( 或 ),其中 允许您覆盖配置回调方法。我们自动 检测是否使用这些适配器类并修改运行时配置 逻辑。@EnableStateMachineStateMachine@ConfigurationEnumStateMachineConfigurerAdapterStateMachineConfigurerAdapterspring-doc.cn

当您需要配置来创建 的实例。@EnableStateMachineFactoryStateMachineFactoryspring-doc.cn

以下部分显示了这些用法示例。

12.2. 配置 State

在本指南的后面部分,我们将介绍更复杂的配置示例,但是 我们首先从简单的事情开始。对于大多数简单的状态 machine 中,您可以使用和定义 可能的状态,然后选择 Initial (初始) 和 Optional End (可选结束状态)。EnumStateMachineConfigurerAdapterspring-doc.cn

@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 枚举,但是,一般来说, 您可以交换字符串和枚举。StateMachineConfigurerAdapterspring-doc.cn

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

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

12.3. 配置分层状态

您可以使用多个调用来定义分层状态,其中 您可以使用它来指示这些 特定状态是其他状态的子状态。 以下示例显示了如何执行此操作:withStates()parent()spring-doc.cn

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

}

12.4. 配置区域

没有特殊的配置方法可以标记 states 作为正交 state 的一部分。简单来说,正交 当同一分层状态机具有多个 set 时创建 state 的状态,每个状态都有一个初始状态。因为单个状态 machine 只能有一个初始状态,多个初始状态必须 表示一个特定的 state 必须有多个独立的 Region。 以下示例显示如何定义区域:spring-doc.cn

@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:StateConfigurerregion(String id)spring-doc.cn

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

}

12.5. 配置过渡

我们支持三种不同类型的过渡:、 和 。转换由 signal 触发 (这是发送到状态机的事件)或定时器。 以下示例显示如何定义所有三种类型的过渡:externalinternallocalspring-doc.cn

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

}

12.6. 配置 Guard

你可以使用 guard 来保护 state transitions。您可以使用该界面 执行方法有权访问 . 以下示例显示了如何执行此操作:GuardStateContextspring-doc.cn

@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 配置。首先,我们 创建了一个 Simple as bean 并将其附加到 states 和 .GuardS1S2spring-doc.cn

其次,我们使用 SPeL 表达式作为守卫来表示 expression 必须返回一个值。在幕后,这个 基于表达式的守卫是一个 .我们将其附加到 状态和 之间的过渡。两个守卫 始终计算为 。BOOLEANSpelExpressionGuardS2S3truespring-doc.cn

12.7. 配置操作

您可以定义要使用过渡和状态执行的操作。 操作始终作为 源自触发器。以下示例说明如何定义操作:spring-doc.cn

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

}

在前面的示例中,single 被定义为名为 和 associated 的 bean 从 到 的过渡。 以下示例演示如何多次使用操作:ActionactionS1S2spring-doc.cn

@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

在前面的示例中,single 由 bean named 和 associated 其中状态 、 和 。我们需要澄清一下这里发生了什么:ActionactionS1S2S3spring-doc.cn

  • 我们为初始状态 定义了一个操作。S1spring-doc.cn

  • 我们为 state 定义了一个 entry 操作,并将 exit 操作留空。S1spring-doc.cn

  • 我们为 state 定义了一个 exit 操作,并将 entry 操作留空。S2spring-doc.cn

  • 我们为状态 定义了一个状态操作。S2spring-doc.cn

  • 我们为 state 定义了 entry 和 exit 动作。S3spring-doc.cn

  • 请注意,state 与 and 函数一起使用两次。仅当您想要定义 entry 或 exit 时,才需要执行此操作 具有初始状态的 action。S1initial()state()spring-doc.cn

使用函数定义操作仅运行特定的 action 来触发。此操作 是仅运行一次的初始化操作。定义的操作 如果状态机转换回来,则运行 with 以及 forward 在初始状态和非初始状态之间。initial()state()

12.7.1. 状态操作

与 entry 和 exit 相比,状态操作的运行方式不同 操作,因为执行发生在进入状态之后 如果 state exit 发生在特定操作之前,则可以取消 已完成。spring-doc.cn

State action 使用正常的响应式 flow 执行,方法是订阅 reactor 的默认并行调度器。这意味着,无论您在 操作,您需要能够捕获,或者更一般地说, 定期检查是否中断。InterruptedExceptionThreadspring-doc.cn

以下示例显示了使用 default 的典型配置,其中 当 state 为 complete 时,将立即取消正在运行的任务:IMMEDIATE_CANCELspring-doc.cn

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

您可以将策略设置为与全局超时一起 对于每台计算机。这会将状态行为更改为 await action completion 在请求取消之前。以下示例显示了如何执行此操作:TIMEOUT_CANCELspring-doc.cn

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

如果直接将机器带入状态,以便事件标头 可用于特定操作,您还可以使用专用的 event 标头设置特定超时(在 中定义)。 您可以将 reserved 标头值用于此目的。以下示例显示了如何执行此操作:EventmillisStateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUTspring-doc.cn

@Autowired
StateMachine<String, String> stateMachine;

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

}

12.7.2. Transition Action 错误处理

您始终可以手动捕获异常。但是,使用 transitions,您可以定义一个 error 操作,该操作在 异常。然后,该异常可从传递给该操作的 a 中获得。以下示例演示如何创建 state 处理异常:StateContextspring-doc.cn

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

}

如果需要,您可以为每个操作手动创建类似的 logic。 以下示例显示了如何执行此操作:spring-doc.cn

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

12.7.3. 状态操作错误处理

还提供类似于处理 state 转换中错误的 logic 的 logic 用于进入状态和退出状态。spring-doc.cn

对于这些情况,具有名为 、 和 的 方法 。这些方法将操作与 normal (non-error) 一起定义。 以下示例演示如何使用所有三种方法:StateConfigurerstateEntrystateDostateExiterroractionspring-doc.cn

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

12.8. 配置伪状态

伪 state 配置通常是通过配置 state 和 转换。伪状态会自动作为 国家。spring-doc.cn

12.8.1. 初始状态

您可以使用该方法将特定状态标记为初始状态。例如,此初始操作适用于初始化 扩展状态变量。以下示例演示如何使用该方法:initial()initial()spring-doc.cn

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

}

12.8.2. 终止状态

您可以使用该方法将特定状态标记为结束状态。 您最多可以为每个子计算机或区域执行此操作一次。 以下示例演示如何使用该方法:end()end()spring-doc.cn

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

}

12.8.3. 状态历史

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

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

}

此外,如前面的示例所示,您可以选择定义默认的 在同一台机器中从历史状态过渡到状态顶点。 此过渡是默认进行的,例如,如果计算机具有 从未被输入,因此,将没有历史记录可用。如果默认的 state transition 未定义,则正常进入 Region 为 做。如果计算机的历史记录为 最终状态。spring-doc.cn

12.8.4. Choice 状态

需要在 state 和 transition to work 中定义 choice 适当地。您可以使用该方法将特定状态标记为选择状态。当转换 配置。choice()spring-doc.cn

您可以使用 配置过渡,其中定义了源 state 和一个结构,它相当于普通的 .使用 和 ,你可以指定一个守卫 就像您将使用带子句的条件一样。withChoice()first/then/lastif/elseif/elsefirstthenif/elseifspring-doc.cn

过渡需要能够存在,因此您必须确保使用 . 否则,配置格式不正确。以下示例显示了如何定义 a choice 状态:lastspring-doc.cn

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

}

操作可以在 Nm 的传入和传出过渡 choice 伪状态。如以下示例所示,一个虚拟 lambda action 的 API API 的 API 的 Lambda 操作(其中它还 定义一个 error 操作):spring-doc.cn

@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
					});
	}
}
Junction 具有相同的 api 格式,这意味着可以定义操作 同样地。

12.8.5. Junction 状态

您需要在状态和转换中定义一个 junction 才能使其正常工作 适当地。您可以使用该方法将特定状态标记为选择状态。当转换 配置。junction()spring-doc.cn

您可以使用定义源的位置来配置过渡 state 和一个结构(相当于 normal )来实现。使用 和 ,可以将守卫指定为 您将使用带子句的 Condition。withJunction()first/then/lastif/elseif/elsefirstthenif/elseifspring-doc.cn

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

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

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

12.8.6. 分叉状态

您必须在 state 和 transitions 中定义 fork 才能正常工作 适当地。您可以使用该方法将特定状态标记为选择状态。当转换 配置。fork()spring-doc.cn

目标状态需要是 super 状态或 immediate 状态中的 地区。使用超级状态作为目标会将所有区域置于 初始状态。以单个状态为目标,入口控制更严格 到区域。以下示例使用 fork:spring-doc.cn

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

}

12.8.7. Join State

您必须在 state 和 transition 中定义一个 join 才能正常工作 适当地。您可以使用该方法将特定状态标记为选择状态。此状态不需要匹配源状态或 target 状态。join()spring-doc.cn

您可以选择当所有源状态时转换转到的目标状态 已加入。如果您使用 State Hosting Regions 作为源,则 区域的状态用作联接。否则,您可以选择任何 州。以下示例使用 join:spring-doc.cn

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

您还可以让多个过渡源自 join 状态。在这种情况下,我们建议你使用 guard 并定义你的 guard 使得在任何给定时间只有一个守卫计算。否则 过渡行为是不可预测的。这在以下示例中显示,其中 guard 检查 extended state 是否有变量:TRUEspring-doc.cn

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

12.8.8. 退出点和入口点状态

您可以使用退出点和入场点来执行更受控的退出和进入操作 从 和 进入 Submachine。 以下示例使用 and 方法定义入口点:withEntrywithExitspring-doc.cn

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

如上所示,您需要将特定状态标记为 being 和 states。然后,创建到这些状态的正常过渡 并指定 和 ,其中这些状态 分别退出和进入。exitentrywithExit()withEntry()spring-doc.cn

12.9. 配置通用设置

您可以使用 设置通用状态机配置的一部分。有了它,您可以设置 和 autostart 标志 对于状态机。它还允许您注册实例、 配置转换冲突策略和区域执行策略。 以下示例演示如何使用:ConfigurationConfigurerBeanFactoryStateMachineListenerConfigurationConfigurerspring-doc.cn

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

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

在配置类中设置只是为了方便 你想或需要在那里做。machineIdspring-doc.cn

注册实例部分也是为了 方便,但如果您想在 状态机生命周期,例如获取状态机的 start 和 stop 事件。请注意,您不能监听 state 计算机的启动事件(如果已启用),除非您注册侦听器 在配置阶段。StateMachineListenerautoStartupspring-doc.cn

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

可用于配置 .它 允许您设置 ,它(如果存在)自动 包装任何使用 和 创建的 启用分布式模式。以下示例演示如何使用它:withDistributed()DistributedStateMachineStateMachineEnsembleStateMachineDistributedStateMachinespring-doc.cn

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

有关分布式状态的更多信息,请参阅使用分布式状态spring-doc.cn

该接口在内部用于 对状态机的结构进行一些健全性检查。其目的是 fail fast,而不是让常见的配置错误进入 状态机。默认情况下,会自动启用验证程序并使用 implementation 。StateMachineModelVerifierDefaultStateMachineModelVerifierspring-doc.cn

使用 ,如果满足以下条件,您可以禁用验证程序或设置自定义验证程序 需要。以下示例显示了如何执行此操作:withVerifier()spring-doc.cn

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

有关配置模型的更多信息,请参见 StateMachine 配置模型spring-doc.cn

的 和 配置方法 分别记录在 State Machine SecurityMonitoring a State MachineUsing StateMachineRuntimePersister 中。withSecuritywithMonitoringwithPersistence

12.10. 配置模型

StateMachineModelFactory是一个钩子,允许您配置 StateMachine 模型 无需使用手动配置。本质上,它是一个第三方 integration 集成到配置模型中。 您可以通过以下方式挂接到配置模型 使用 .以下示例显示了如何执行此操作:StateMachineModelFactoryStateMachineModelConfigurerspring-doc.cn

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

以下示例用于 定义两个状态 ( 和 ) 以及它们之间的事件 () 国家:CustomStateMachineModelFactoryS1S2E1spring-doc.cn

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 Modeling Support 中找到使用此 Model Factory 集成的示例。您可以找到有关自定义模型集成的更多通用信息 在 Developer Documentation(开发人员文档)中。spring-doc.cn

12.11. 需要记住的事情

当从 配置,记住 Spring Framework 的工作原理是值得的 和豆子。在下一个示例中,我们定义了一个普通配置,其中 状态 和 4 个过渡。所有过渡 由 或 保护。您必须确保将其创建为真正的 bean,因为它带有 , while is 不是。S1S2guard1guard2guard1@Beanguard2spring-doc.cn

这意味着该事件将获得条件 为 ,并将获得条件为 ,因为这些条件是 来自对这些函数的普通方法调用。E3guard2TRUEE4guard2FALSEspring-doc.cn

但是,由于 被定义为 ,它由 Spring 框架。因此,对其方法的额外调用会导致 只有该实例的一个实例。Event 将首先获取 具有条件的代理实例,而 event 将得到相同的 实例 和 条件 (当方法调用是使用 定义时)。这不是 Spring State Machine 特有的行为。相反,它是 Spring Framework 如何与 bean 一起工作。 以下示例显示了这种安排的工作原理:guard1@BeanE1TRUEE2TRUEFALSEspring-doc.cn

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

13. 状态机 ID

各种类和接口用作变量或 parameter 的 Method。本节将仔细研究与正常机器操作和实例化的关系。machineIdmachineIdspring-doc.cn

在运行时,确实没有任何大的操作 role 除外,以区分机器 — 例如,当 跟踪日志或进行更深入的调试。有很多不同的 如果有 Machine Instances 的话,开发人员很快就会迷失在翻译中 没有简单的方法来识别这些实例。因此,我们添加了将 .machineIdmachineIdspring-doc.cn

13.1. 使用@EnableStateMachine

在 Java 配置中设置 as then 会公开该值 用于日志。该方法也同样可用。以下示例使用该方法:machineIdmymachinemachineIdStateMachine.getId()machineIdspring-doc.cn

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
		throws Exception {
	config
		.withConfiguration()
			.machineId("mymachine");
}

以下日志输出示例显示了 ID:mymachinespring-doc.cn

11:23:54,509  INFO main support.LifecycleObjectSupport [main] -
started S2 S1  / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine
手动构建器(参见 通过 Builder 进行状态机)使用相同的配置 接口,这意味着行为是等效的。

13.2. 使用@EnableStateMachineFactory

如果您使用 并使用该 ID 请求新计算机,则可以看到相同的配置。 如下例所示:machineIdStateMachineFactoryspring-doc.cn

StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
StateMachine<String, String> machine = factory.getStateMachine("mymachine");

13.3. 使用StateMachineModelFactory

在幕后,所有机器配置首先被转换为 so that need need know 从配置的来源,因为机器可以构建 Java 配置、UML 或存储库。如果你想疯狂,你也可以使用 自定义 ,这是尽可能最低的 定义配置的级别。StateMachineModelStateMachineFactoryStateMachineModelspring-doc.cn

所有这些都与 a 有什么关系? 还有一个具有以下签名的方法: 实现可以选择使用该方法。machineIdStateMachineModelFactoryStateMachineModel<S, E> build(String machineId)StateMachineModelFactoryspring-doc.cn

RepositoryStateMachineModelFactory(请参阅存储库支持)用于支持持久 store 通过 Spring Data Repository 接口。例如,both 和 都有一个方法 (),用于构建不同的状态和 过渡 .其中 , 如果用作空 或 NULL,则默认为 repository configuration(在后备持久模型中) 没有已知的计算机 ID。machineIdStateRepositoryTransitionRepositoryList<T> findByMachineId(String machineId)machineIdRepositoryStateMachineModelFactorymachineIdspring-doc.cn

目前,不区分 不同的计算机 ID,因为 UML 源总是来自同一 文件。在未来的版本中,这可能会发生变化。UmlStateMachineModelFactory

14. 国家机工厂

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

14.1. 通过适配器出厂

实际上,使用 works 创建状态机是通过工厂进行的,因此只需公开 该工厂通过其接口。以下示例使用 :@EnableStateMachine@EnableStateMachineFactory@EnableStateMachineFactoryspring-doc.cn

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

}

现在您已经习惯了创建工厂 你可以注入它并(按原样)使用它来 请求新的状态机。以下示例显示了如何执行此操作:@EnableStateMachineFactoryspring-doc.cn

public class Bean3 {

	@Autowired
	StateMachineFactory<States, Events> factory;

	void method() {
		StateMachine<States,Events> stateMachine = factory.getStateMachine();
		stateMachine.startReactively().subscribe();
	}
}

14.1.1. 适配器出厂限制

工厂目前的局限性是它的所有动作和防护 关联一个状态机共享同一个实例。 这意味着,从你的行动和警卫来看,你需要 专门处理同一个 bean 被不同的 bean 调用 状态机。此限制将在 未来版本。spring-doc.cn

14.2. 通过 Builder 进行状态机

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

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();
}

构建器在后台使用相同的配置接口,这些接口 该模型用于 Adapter 类。相同的模型用于 通过构建器的 方法。这意味着你可以与法线一起使用,或者你可以通过 builder 动态使用。@ConfigurationEnumStateMachineConfigurerAdapterStateMachineConfigurerAdapterspring-doc.cn

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

以下示例使用 builder 设置许多选项:spring-doc.cn

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

您需要了解常见配置何时需要 用于从 Builder 实例化的计算机。您可以使用 configurer 从 a 返回到 setup 和 。 您还可以使用一个 API 注册 .如果使用 将生成器返回的实例注册为 Bean,则会自动附加 。如果您在 Spring 应用程序上下文之外使用实例,则 您必须使用这些方法来设置所需的设施。withConfiguration()autoStartBeanFactoryStateMachineListenerStateMachine@BeanBeanFactoryspring-doc.cn

15. 使用延迟事件

发送事件时,它可能会触发 ,这可能会导致 如果状态机处于触发器为 评估成功。通常,这可能会导致 事件未被接受并被丢弃。但是,您可能希望 将此事件推迟到状态机进入另一个状态。在这种情况下, 您可以接受该事件。换句话说,一个事件 来得不是时候。EventTriggerspring-doc.cn

Spring Statemachine 提供了一种将事件推迟到以后的机制 加工。每个状态都可以有一个延迟事件列表。如果事件 在当前状态的 Deferred Event List occurs 中,保存该事件 (deferred) 以供将来处理,直到输入未列出的状态 其 Deferred Event 列表中的事件。当进入此类状态时, 状态机会自动调用任何已保存的不再 deferred,然后使用或丢弃这些事件。这是可能的 使 Superstate 在延迟的事件上定义转换 按子状态。遵循相同的分层状态机概念,子状态 优先于超状态,则事件被延迟,并且 transi 的 TRANSITION 未运行。对于正交区域, 当一个正交区域推迟事件而另一个正交区域接受事件时, accept 优先,事件被使用而不是延迟。spring-doc.cn

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

@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 处理事件,这些事件会使其进入某种状态,其中 实际部署将发生。运行部署操作后,计算机 将返回到状态。如果机器正在使用同步执行程序,则以一种状态发送多个事件不会造成任何问题。 因为事件发送会在事件调用之间阻塞。但是,如果 executor 使用 threads 中,其他事件可能会丢失,因为机器不再处于 可以处理事件。因此,延迟其中一些事件可以让机器 保留它们。以下示例说明如何配置此类安排:READYDEPLOYREADYREADYspring-doc.cn

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

在前面的示例中,状态机使用嵌套状态而不是 flat state 模型,因此可以直接在 substate 中延迟事件。 它还演示了在 子状态,然后覆盖 AND 表示状态机是否恰好处于 dispatch 事件时的状态。在事件未延迟的状态下,此事件将 在超级状态下处理。DEPLOYDONEDEPLOYDONEDEPLOYPREPAREDONEDEPLOYEXECUTEDONEspring-doc.cn

16. 使用范围

对状态机中范围的支持非常有限,但您可以 通过以下两种方式之一使用普通的 Spring 注释来启用范围:session@Scopespring-doc.cn

  • 如果状态机是使用构建器手动构建的,并返回到 context 设置为 .@Beanspring-doc.cn

  • 通过配置适配器。spring-doc.cn

两者 这些需要存在,并且设置为 和 设置为。以下示例 显示两个用例:@ScopescopeNamesessionproxyModeScopedProxyMode.TARGET_CLASSspring-doc.cn

@Configuration
public class Config3 {

	@Bean
	@Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
	StateMachine<String, String> stateMachine() throws Exception {
		Builder<String, String> builder = StateMachineBuilder.builder();
		builder.configureConfiguration()
			.withConfiguration()
				.autoStartup(true);
		builder.configureStates()
			.withStates()
				.initial("S1")
				.state("S2");
		builder.configureTransitions()
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
		StateMachine<String, String> stateMachine = builder.build();
		return stateMachine;
	}

}
@Configuration
@EnableStateMachine
@Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public static class Config4 extends StateMachineConfigurerAdapter<String, String> {

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

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

}

提示:有关如何使用会话范围的信息,请参阅 Scopespring-doc.cn

将状态机的范围限定为 后,将其自动装配到 a 为每个会话提供一个新的状态机实例。 然后,每个状态机在失效时被销毁。 以下示例展示了如何在控制器中使用状态机:session@ControllerHttpSessionspring-doc.cn

@Controller
public class StateMachineController {

	@Autowired
	StateMachine<String, String> stateMachine;

	@RequestMapping(path="/state", method=RequestMethod.POST)
	public HttpEntity<Void> setState(@RequestParam("event") String event) {
		stateMachine
			.sendEvent(Mono.just(MessageBuilder
				.withPayload(event).build()))
			.subscribe();
		return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
	}

	@RequestMapping(path="/state", method=RequestMethod.GET)
	@ResponseBody
	public String getState() {
		return stateMachine.getState().getId();
	}
}
在 scope 中使用状态机需要仔细规划, 主要是因为它是一个相对较重的组件。session
Spring Statemachine poms 不依赖于 Spring MVC 类,您需要使用 session 范围。但是,如果你是 使用 Web 应用程序时,您已经拉取了这些依赖项 直接从 Spring MVC 或 Spring Boot 获取。

17. 使用操作

操作是可用于的最有用的组件之一 与状态机交互和协作。您可以运行操作 在状态机及其状态生命周期的不同位置——例如, 进入或退出状态或在过渡期间。 以下示例显示了如何在状态机中使用操作:spring-doc.cn

@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
		throws Exception {
	states
		.withStates()
			.initial(States.SI)
			.state(States.S1, action1(), action2())
			.state(States.S2, action1(), action2())
			.state(States.S3, action1(), action3());
}

在前面的示例中,和 bean 分别附加到 和 state。以下示例定义了这些操作 (和 ):action1action2entryexitaction3spring-doc.cn

@Bean
public Action<States, Events> action1() {
	return new Action<States, Events>() {

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

@Bean
public BaseAction action2() {
	return new BaseAction();
}

@Bean
public SpelAction action3() {
	ExpressionParser parser = new SpelExpressionParser();
	return new SpelAction(
			parser.parseExpression(
					"stateMachine.sendEvent(T(org.springframework.statemachine.docs.Events).E1)"));
}

public class BaseAction implements Action<States, Events> {

	@Override
	public void execute(StateContext<States, Events> context) {
	}
}

public class SpelAction extends SpelExpressionAction<States, Events> {

	public SpelAction(Expression expression) {
		super(expression);
	}
}

您可以直接实现为匿名函数或创建 您自己的实现,并将相应的实现定义为 豆。Actionspring-doc.cn

在前面的示例中,使用 SpEL 表达式将事件发送到 状态机。action3Events.E1spring-doc.cn

StateContext使用 StateContext 中进行了介绍。

17.1. 带有动作的 SPEL 表达式

您还可以使用 SPEL 表达式来替代 完全实施。Actionspring-doc.cn

17.2. 响应式 Action

Normal interface 是一种简单的函数式方法,取回 void。在你阻止之前,这里没有任何阻碍 在方法本身中,这是一个有点问题,因为框架不能 了解它内部到底发生了什么。ActionStateContextspring-doc.cn

public interface Action<S, E> {
	void execute(StateContext<S, E> context);
}

为了解决这个问题,我们在内部将处理方式更改为 处理普通 Java 的 take 和 return .这样我们就可以调用 action 并完全以响应式方式 execute 操作,仅当它被订阅且以非阻塞方式执行 等待完成。ActionFunctionStateContextMonospring-doc.cn

public interface ReactiveAction<S, E> extends Function<StateContext<S, E>, Mono<Void>> {
}

内部旧接口由 Reactor Mono Runnable 包装,因为它 共享相同的 return 类型。我们无法控制您在该方法中做什么!Actionspring-doc.cn

18. 使用守卫

Things to Remember 中所示,和 beans 附加到条目和 退出状态。 以下示例还对事件使用 guards:guard1guard2spring-doc.cn

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
		throws Exception {
	transitions
		.withExternal()
			.source(States.SI).target(States.S1)
			.event(Events.E1)
			.guard(guard1())
			.and()
		.withExternal()
			.source(States.S1).target(States.S2)
			.event(Events.E1)
			.guard(guard2())
			.and()
		.withExternal()
			.source(States.S2).target(States.S3)
			.event(Events.E2)
			.guardExpression("extendedState.variables.get('myvar')");
}

您可以直接实现为匿名函数或创建 您自己的实现,并将相应的实现定义为 豆。在前面的示例中,检查S是否扩展了 state 变量 named 的计算结果为 。 下面的示例实现一些示例 guards:GuardguardExpressionmyvarTRUEspring-doc.cn

@Bean
public Guard<States, Events> guard1() {
	return new Guard<States, Events>() {

		@Override
		public boolean evaluate(StateContext<States, Events> context) {
			return true;
		}
	};
}

@Bean
public BaseGuard guard2() {
	return new BaseGuard();
}

public class BaseGuard implements Guard<States, Events> {

	@Override
	public boolean evaluate(StateContext<States, Events> context) {
		return false;
	}
}
StateContext使用 StateContext 部分中进行了介绍。

18.1. 带有守卫的 SPEL 表达式

您还可以使用 SPEL 表达式来替代 完整的 Guard 实现。唯一的要求是表达式需要 返回一个值来满足实现。这可以是 演示使用采用 expression 作为参数。BooleanGuardguardExpression()spring-doc.cn

18.2. 响应式守卫

Normal 接口是一个简单的函数式方法,获取并返回布尔值。在你阻止之前,这里没有任何阻碍 在方法本身中,这是一个有点问题,因为框架不能 了解它内部到底发生了什么。GuardStateContextspring-doc.cn

public interface Guard<S, E> {
	boolean evaluate(StateContext<S, E> context);
}

为了解决这个问题,我们在内部将处理方式更改为 处理普通 Java 的 take 和 return .这样我们就可以调用 guard 并且完全以响应方式 仅在订阅时以非阻塞方式对其进行评估 等待 return 值完成。GuardFunctionStateContextMono<Boolean>spring-doc.cn

public interface ReactiveGuard<S, E> extends Function<StateContext<S, E>, Mono<Boolean>> {
}

内部 old 接口由 Reactor Mono Function 包装。我们没有 控制您在该方法中执行的操作!Guardspring-doc.cn

19. 使用扩展状态

假设您需要创建一个状态机来跟踪 很多时候,用户按下键盘上的某个键,然后终止 当按键被按下 1000 次时。一个可能但非常幼稚的解决方案 将是每按 1000 次按键创建一个新状态。 您可能会突然出现一个天文数字 状态,这自然不是很实用。spring-doc.cn

这就是扩展状态变量通过不需要 以添加更多状态来驱动状态机更改。相反 您可以在过渡期间执行简单的变量更改。spring-doc.cn

StateMachine有一个名为 .它返回一个 名为 的接口,该接口提供对扩展状态的访问 变量。您可以通过状态机直接访问这些变量,也可以在操作或转换的回调期间访问这些变量。 以下示例显示了如何执行此操作:getExtendedState()ExtendedStateStateContextspring-doc.cn

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

		@Override
		public void execute(StateContext<String, String> context) {
			context.getExtendedState()
				.getVariables().put("mykey", "myvalue");
		}
	};
}

如果您需要获得扩展状态变量的通知 changes,您有两个选项:使用 或 监听回调。以下示例 使用的方法如下:StateMachineListenerextendedStateChanged(key, value)extendedStateChangedspring-doc.cn

public class ExtendedStateVariableListener
		extends StateMachineListenerAdapter<String, String> {

	@Override
	public void extendedStateChanged(Object key, Object value) {
		// do something with changed variable
	}
}

或者,您可以为 实现 Spring Application 上下文侦听器。如 侦听状态机事件 中所述, 您还可以监听所有事件。 以下示例用于侦听状态更改:OnExtendedStateChangedStateMachineEventonApplicationEventspring-doc.cn

public class ExtendedStateVariableEventListener
		implements ApplicationListener<OnExtendedStateChanged> {

	@Override
	public void onApplicationEvent(OnExtendedStateChanged event) {
		// do something with changed variable
	}
}

20. 使用StateContext

StateContext 是最重要的对象之一 当使用状态机时,因为它被传递到各种方法中 和回调来给出状态机的当前状态,以及 它可能去哪里。您可以将其视为 当前状态机阶段的快照 是 Retreived 的时间。StateContextspring-doc.cn

在 Spring Statemachine 1.0.x 中,使用相对幼稚 就它如何被用来作为简单的 “POJO” 传递东西而言。 从 Spring Statemachine 1.1.x 开始,它的作用已经大大 通过使其成为状态机中的一等公民而得到改进。StateContext

您可以使用 来访问以下内容:StateContextspring-doc.cn

StateContext传递到各种组件中,例如 和 。ActionGuardspring-doc.cn

20.1. 阶段

Stage 是 on 的表示形式 哪个状态机当前正在与用户交互。当前可用的 阶段包括 、 、 、 、 和 。这些状态可能看起来很熟悉,因为 它们与您与侦听器的交互方式相匹配(如侦听状态机事件中所述)。stageEVENT_NOT_ACCEPTEDEXTENDED_STATE_CHANGEDSTATE_CHANGEDSTATE_ENTRYSTATE_EXITSTATEMACHINE_ERRORSTATEMACHINE_STARTSTATEMACHINE_STOPTRANSITIONTRANSITION_STARTTRANSITION_ENDspring-doc.cn

21. 触发过渡

驱动状态机是通过使用触发的转换来完成的 by 触发器。当前支持的触发器包括 和 。EventTriggerTimerTriggerspring-doc.cn

21.1. 使用EventTrigger

EventTrigger是最有用的触发器,因为它允许您 通过向状态机发送事件来直接与状态机交互。这些 事件也称为信号。您可以向过渡添加触发器 通过在配置期间将状态与其关联。 以下示例显示了如何执行此操作:spring-doc.cn

@Autowired
StateMachine<String, String> stateMachine;

void signalMachine() {
	stateMachine
		.sendEvent(Mono.just(MessageBuilder
			.withPayload("E1").build()))
		.subscribe();

	Message<String> message = MessageBuilder
			.withPayload("E2")
			.setHeader("foo", "bar")
			.build();
	stateMachine.sendEvent(Mono.just(message)).subscribe();
}

无论您是发送一个事件还是多个事件,结果始终是一个序列 的结果。之所以如此,是因为在存在多个 reqions 的情况下,结果将 从这些区域中的多台计算机返回。这是 with 方法,该方法给出结果列表。方法 本身只是一个语法 Sugar Collecting As 列表。如果有 只有一个地区,此列表包含一个结果。sendEventCollectFluxspring-doc.cn

Message<String> message1 = MessageBuilder
	.withPayload("E1")
	.build();

Mono<List<StateMachineEventResult<String, String>>> results =
	stateMachine.sendEventCollect(Mono.just(message1));

results.subscribe();
在订阅返回的 flux 之前,什么都不会发生。从 StateMachineEventResult 中了解更多信息。

前面的示例通过构造 wrapping 来发送事件 a 并订阅返回的结果。 让 我们向事件添加任意的额外信息,然后该信息可见 到实施操作的时间(例如)。MonoMessageFluxMessageStateContextspring-doc.cn

消息标头通常会一直传递,直到机器运行 完成特定事件。例如,如果事件导致 transition 转换为 state 中,该 state 具有匿名 transition 到 state 中,原始事件可用于 state 中的操作或守卫。ABB

也可以发送消息,而不仅仅是发送 一个带有 .FluxMonospring-doc.cn

Message<String> message1 = MessageBuilder
	.withPayload("E1")
	.build();
Message<String> message2 = MessageBuilder
	.withPayload("E2")
	.build();

Flux<StateMachineEventResult<String, String>> results =
	stateMachine.sendEvents(Flux.just(message1, message2));

results.subscribe();

21.1.1. StateMachineEventResult

StateMachineEventResult包含有关结果的更多详细信息 的事件发送。从中,您可以获得处理事件的 ITS 本身以及实际的 .来自您 可以查看邮件是被接受、被拒绝还是延迟。一般来说,当 订阅完成,事件将传递到计算机中。RegionMessageResultTypeResultTypespring-doc.cn

21.2. 使用TimerTrigger

TimerTrigger在需要触发某些操作时很有用 自动的。 已添加到 transition 的 TRANSITION。Triggerspring-doc.cn

目前,有两种类型的受支持的计时器,一种是触发 持续触发,并在进入源状态后触发。 以下示例演示如何使用触发器:spring-doc.cn

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

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

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S1").target("S2").event("E1")
				.and()
			.withExternal()
				.source("S1").target("S3").event("E2")
				.and()
			.withInternal()
				.source("S2")
				.action(timerAction())
				.timer(1000)
				.and()
			.withInternal()
				.source("S3")
				.action(timerAction())
				.timerOnce(1000);
	}

	@Bean
	public TimerAction timerAction() {
		return new TimerAction();
	}
}

public class TimerAction implements Action<String, String> {

	@Override
	public void execute(StateContext<String, String> context) {
		// do something in every 1 sec
	}
}

前面的示例有三种状态:、 和 。我们有一个正常的 从 to 和 from to with 的外部过渡 events 和 。有趣的部分 因为使用 ARE 时,我们定义 源状态和 的内部转换。S1S2S3S1S2S1S3E1E2TimerTriggerS2S3spring-doc.cn

对于这两个转换,我们调用 bean () ),其中 源状态使用 和 使用 。 给出的值以毫秒为单位(在两种情况下都是毫秒或 1 秒)。ActiontimerActionS2timerS3timerOnce1000spring-doc.cn

一旦状态机收到 event ,它就会执行转换 from to 和计时器开始。当状态为 时,会运行并导致与该 state — 在本例中,是具有 defined.E1S1S2S2TimerTriggertimerActionspring-doc.cn

一旦状态机收到 , 事件,它就会执行转换 from to 和计时器开始。此计时器仅执行一次 在 state 被输入之后 (在 timer 中定义的 delay 之后)。E2S1S3spring-doc.cn

在幕后,计时器是简单的触发器,可能会导致 transition 到 Happen 的 Transition。使用 keep 定义过渡 仅当 source state 为 active 时,触发才会触发并导致 transition。 Transition with 略有不同,因为它 仅在实际进入源状态时延迟后触发。timer()timerOnce()
如果您希望在延迟后发生某些事情,请使用 在 State 进入时恰好一次。timerOnce()

22. 监听状态机事件

在一些用例中,您想知道发生了什么 状态机、对某事做出反应或获取 调试目的。Spring Statemachine 提供了用于添加侦听器的接口。这些侦听器 然后给出一个选项,以便在各种 state 发生变化时获取回调, 操作,等等。spring-doc.cn

您基本上有两个选择:监听 Spring 应用程序 context 事件或直接将侦听器附加到状态机。两者 这些基本上提供相同的信息。一个产生 events 作为事件类,另一个通过侦听器生成回调 接口。这两者都有优点和缺点,我们将在后面讨论。spring-doc.cn

22.1. 应用程序上下文事件

应用程序上下文事件类包括 、 、 、这些可以按原样与 Spring 一起使用。OnTransitionStartEventOnTransitionEventOnTransitionEndEventOnStateExitEventOnStateEntryEventOnStateChangedEventOnStateMachineStartOnStateMachineStopStateMachineEventApplicationListenerspring-doc.cn

StateMachine通过 发送上下文事件。 如果类使用 . 下面的示例从类中定义的 bean 获取一个:StateMachineEventPublisher@Configuration@EnableStateMachineStateMachineApplicationEventListener@Configurationspring-doc.cn

public class StateMachineApplicationEventListener
		implements ApplicationListener<StateMachineEvent> {

	@Override
	public void onApplicationEvent(StateMachineEvent event) {
	}
}

@Configuration
public class ListenerConfig {

	@Bean
	public StateMachineApplicationEventListener contextListener() {
		return new StateMachineApplicationEventListener();
	}
}

上下文事件也会通过使用 自动启用。 用于构建机器并注册为 Bean, 如下例所示:@EnableStateMachineStateMachinespring-doc.cn

@Configuration
@EnableStateMachine
public class ManualBuilderConfig {

	@Bean
	public StateMachine<String, String> stateMachine() throws Exception {

		Builder<String, String> builder = StateMachineBuilder.builder();
		builder.configureStates()
			.withStates()
				.initial("S1")
				.state("S2");
		builder.configureTransitions()
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
		return builder.build();
	}
}

22.2. 使用StateMachineListener

通过使用 ,您可以扩展它和 实现所有回调方法或使用包含存根方法实现的类并选择哪些实现 以覆盖。 以下示例使用后一种方法:StateMachineListenerStateMachineListenerAdapterspring-doc.cn

public class StateMachineEventListener
		extends StateMachineListenerAdapter<States, Events> {

	@Override
	public void stateChanged(State<States, Events> from, State<States, Events> to) {
	}

	@Override
	public void stateEntered(State<States, Events> state) {
	}

	@Override
	public void stateExited(State<States, Events> state) {
	}

	@Override
	public void transition(Transition<States, Events> transition) {
	}

	@Override
	public void transitionStarted(Transition<States, Events> transition) {
	}

	@Override
	public void transitionEnded(Transition<States, Events> transition) {
	}

	@Override
	public void stateMachineStarted(StateMachine<States, Events> stateMachine) {
	}

	@Override
	public void stateMachineStopped(StateMachine<States, Events> stateMachine) {
	}

	@Override
	public void eventNotAccepted(Message<Events> event) {
	}

	@Override
	public void extendedStateChanged(Object key, Object value) {
	}

	@Override
	public void stateMachineError(StateMachine<States, Events> stateMachine, Exception exception) {
	}

	@Override
	public void stateContext(StateContext<States, Events> stateContext) {
	}
}

在前面的示例中,我们创建了自己的 listener 类 () 扩展 .StateMachineEventListenerStateMachineListenerAdapterspring-doc.cn

listener 方法允许访问不同阶段上的各种更改。您可以在使用 StateContext 中找到有关它的更多信息。stateContextStateContextspring-doc.cn

定义自己的侦听器后,您可以在 状态机。这是一个 flavor 是将其挂接在 Spring 配置中还是执行 在应用程序生命周期内的任何时间手动操作。 以下示例显示如何附加侦听器:addStateListenerspring-doc.cn

public class Config7 {

	@Autowired
	StateMachine<States, Events> stateMachine;

	@Bean
	public StateMachineEventListener stateMachineEventListener() {
		StateMachineEventListener listener = new StateMachineEventListener();
		stateMachine.addStateListener(listener);
		return listener;
	}

}

22.3. 限制和问题

Spring 应用程序上下文并不是最快的事件总线,因此我们 建议考虑一下状态机的事件发生率 发送。为了获得更好的性能,最好使用该界面。出于这个特定的原因, 您可以将标志与 和 一起使用来禁用 Spring 应用程序上下文 事件,如上一节所示。 以下示例显示如何禁用 Spring 应用程序上下文事件:StateMachineListenercontextEvents@EnableStateMachine@EnableStateMachineFactoryspring-doc.cn

@Configuration
@EnableStateMachine(contextEvents = false)
public class Config8
		extends EnumStateMachineConfigurerAdapter<States, Events> {
}

@Configuration
@EnableStateMachineFactory(contextEvents = false)
public class Config9
		extends EnumStateMachineConfigurerAdapter<States, Events> {
}

23. 上下文集成

通过以下方式与状态机进行交互有点受限 监听其事件或使用带有 state 和 转换。有时,这种方法会过于有限,并且 verbose 创建与状态机的应用程序的交互 工程。对于这个特定的用例,我们制作了一个 Spring 风格的 轻松插入状态机功能的上下文集成 放入你的豆子里。spring-doc.cn

可用的注释已协调,以便能够访问相同的 可从 Listening to State Machine Events 获得的状态机执行点。spring-doc.cn

您可以使用注释来关联状态 machine 中。然后你就可以开始添加 该 bean 的方法的 supported 注释。 以下示例显示了如何执行此操作:@WithStateMachinespring-doc.cn

@WithStateMachine
public class Bean1 {

	@OnTransition
	public void anyTransition() {
	}
}

您还可以从 Application context 结合使用。 以下示例显示了如何执行此操作:namespring-doc.cn

@WithStateMachine(name = "myMachineBeanName")
public class Bean2 {

	@OnTransition
	public void anyTransition() {
	}
}

有时,使用起来更方便,这是一些事情 您可以设置以更好地识别多个实例。此 ID 映射到 接口中的方法。 以下示例演示如何使用它:machine idgetId()StateMachinespring-doc.cn

@WithStateMachine(id = "myMachineId")
public class Bean16 {

	@OnTransition
	public void anyTransition() {
	}
}

当使用 StateMachineFactory 生成状态机时,使用 dynamic provided 的状态机,bean name 将默认为无法使用,因为仅在运行时才知道。idstateMachine@WithStateMachine (id = "some-id")idspring-doc.cn

在这种情况下,使用 or 和工厂生成的所有状态机都将附加到您的一个或多个 bean。@WithStateMachine@WithStateMachine(name = "stateMachine")spring-doc.cn

您也可以用作元注释,如图所示 在前面的示例中。在这种情况下,您可以使用 . 以下示例显示了如何执行此操作:@WithStateMachineWithMyBeanspring-doc.cn

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@WithStateMachine(name = "myMachineBeanName")
public @interface WithMyBean {
}
这些方法的返回类型无关紧要,并且实际上是 丢弃。

23.1. 启用集成

您可以使用 注解,它导入所需的 配置导入到 Spring Application Context 中。两者都已经是 使用此注释进行注释,因此无需再次添加它。 但是,如果机器在构建和配置时没有 配置适配器,则必须使用 才能将这些功能与 一起使用。 以下示例显示了如何执行此操作:@WithStateMachine@EnableWithStateMachine@EnableStateMachine@EnableStateMachineFactory@EnableWithStateMachine@WithStateMachinespring-doc.cn

public static StateMachine<String, String> buildMachine(BeanFactory beanFactory) throws Exception {
	Builder<String, String> builder = StateMachineBuilder.builder();

	builder.configureConfiguration()
		.withConfiguration()
			.machineId("myMachineId")
			.beanFactory(beanFactory);

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

	builder.configureTransitions()
		.withExternal()
			.source("S1")
			.target("S2")
			.event("E1");

	return builder.build();
}

@WithStateMachine(id = "myMachineId")
static class Bean17 {

	@OnStateChanged
	public void onStateChanged() {
	}
}
如果机器不是作为 Bean 创建的,则需要为机器进行设置,如前面的示例所示。否则,tge 机器为 不知道调用您的方法的处理程序。BeanFactory@WithStateMachine

23.2. 方法参数

每个 Comments 都支持完全相同的可能方法 参数,但运行时行为会有所不同,具体取决于 annotation 本身以及调用 annotated 方法的阶段。自 更好地了解 context 的工作原理,请参阅使用 StateContextspring-doc.cn

有关方法参数之间的差异,请参阅定义 单独的注释。

实际上,所有带 Comments 的方法都是通过使用 Spring SPel 来调用的 表达式,这些表达式在此过程中动态构建。要使 this work,这些表达式需要有一个 root 对象(它们根据该对象进行求值)。 此根对象是一个 .我们还制作了一些 在内部进行调整,以便可以访问方法 直接访问,而无需通过上下文句柄。StateContextStateContextspring-doc.cn

最简单的方法参数是 a 本身。 以下示例演示如何使用它:StateContextspring-doc.cn

@WithStateMachine
public class Bean3 {

	@OnTransition
	public void anyTransition(StateContext<String, String> stateContext) {
	}
}

您可以访问其余内容。 参数的数量和顺序无关紧要。 以下示例显示了如何访问内容的各个部分:StateContextStateContextspring-doc.cn

@WithStateMachine
public class Bean4 {

	@OnTransition
	public void anyTransition(
			@EventHeaders Map<String, Object> headers,
			@EventHeader("myheader1") Object myheader1,
			@EventHeader(name = "myheader2", required = false) String myheader2,
			ExtendedState extendedState,
			StateMachine<String, String> stateMachine,
			Message<String> message,
			Exception e) {
	}
}
您可以使用 ,而不是使用 获取所有事件标头,它可以绑定到单个标头。@EventHeaders@EventHeader

23.3. 过渡注解

过渡的注释是 、 、 和。@OnTransition@OnTransitionStart@OnTransitionEndspring-doc.cn

这些注释的行为完全相同。为了展示它们的工作原理,我们展示了 如何使用。在此注解中,属性的 您可以使用 和 来限定过渡。如果 和 留空,则匹配任何过渡。 以下示例演示如何使用注释 (记住这一点,并以相同的方式工作):@OnTransitionsourcetargetsourcetarget@OnTransition@OnTransitionStart@OnTransitionEndspring-doc.cn

@WithStateMachine
public class Bean5 {

	@OnTransition(source = "S1", target = "S2")
	public void fromS1ToS2() {
	}

	@OnTransition
	public void anyTransition() {
	}
}

默认情况下,不能将注解与 state 和 由于 Java 语言限制,您创建的 event 枚举。 因此,您需要使用字符串表示形式。@OnTransitionspring-doc.cn

此外,您还可以通过将所需的参数添加到方法来访问 AND。方法 然后,使用这些参数自动调用。 以下示例显示了如何执行此操作:Event HeadersExtendedStatespring-doc.cn

@WithStateMachine
public class Bean6 {

	@StatesOnTransition(source = States.S1, target = States.S2)
	public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
	}
}

但是,如果您想拥有类型安全的注解,则可以 创建新注释并用作元注释。 此用户级注释可以引用实际状态和 events 枚举,框架会尝试以相同的方式匹配这些枚举。 以下示例显示了如何执行此操作:@OnTransitionspring-doc.cn

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {

	States[] source() default {};

	States[] target() default {};
}

在前面的示例中,我们创建了一个注释,该注释以类型安全的方式定义 AND。 下面的示例在 bean 中使用该 Comments:@StatesOnTransitionsourcetargetspring-doc.cn

@WithStateMachine
public class Bean7 {

	@StatesOnTransition(source = States.S1, target = States.S2)
	public void fromS1ToS2() {
	}
}

23.4. 状态注解

状态的以下注释可用:、 和 。以下示例演示如何使用注解( 其他两个的工作方式相同):@OnStateChanged@OnStateEntry@OnStateExitOnStateChangedspring-doc.cn

@WithStateMachine
public class Bean8 {

	@OnStateChanged
	public void anyStateChange() {
	}
}

就像使用 Transition Annotations 一样,您可以定义 target 和 source 状态。以下示例显示了如何执行此操作:spring-doc.cn

@WithStateMachine
public class Bean9 {

	@OnStateChanged(source = "S1", target = "S2")
	public void stateChangeFromS1toS2() {
	}
}

为了类型安全,需要用作元注释为枚举创建新的注释。以下示例说明如何执行此操作:@OnStateChangedspring-doc.cn

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnStateChanged
public @interface StatesOnStates {

	States[] source() default {};

	States[] target() default {};
}
@WithStateMachine
public class Bean10 {

	@StatesOnStates(source = States.S1, target = States.S2)
	public void fromS1ToS2() {
	}
}

state entry 和 exit 的方法的行为方式相同,如下例所示:spring-doc.cn

@WithStateMachine
public class Bean11 {

	@OnStateEntry
	public void anyStateEntry() {
	}

	@OnStateExit
	public void anyStateExit() {
	}
}

23.5. 事件注解

有一个与事件相关的注释。它被命名为 。 如果指定了该属性,则可以侦听特定事件 接受。如果未指定事件,则可以列出任何非 接受。以下示例显示了使用 Comments 的两种方法:@OnEventNotAcceptedevent@OnEventNotAcceptedspring-doc.cn

@WithStateMachine
public class Bean12 {

	@OnEventNotAccepted
	public void anyEventNotAccepted() {
	}

	@OnEventNotAccepted(event = "E1")
	public void e1EventNotAccepted() {
	}
}

23.6. 状态机注解

以下注释可用于状态机:、 和 。@OnStateMachineStart@OnStateMachineStop@OnStateMachineErrorspring-doc.cn

在状态机启动和停止期间,将调用生命周期方法。 下面的示例演示如何使用和侦听这些事件:@OnStateMachineStart@OnStateMachineStopspring-doc.cn

@WithStateMachine
public class Bean13 {

	@OnStateMachineStart
	public void onStateMachineStart() {
	}

	@OnStateMachineStop
	public void onStateMachineStop() {
	}
}

如果状态机出现异常错误,则调用 annotation 。以下示例演示如何使用它:@OnStateMachineStopspring-doc.cn

@WithStateMachine
public class Bean14 {

	@OnStateMachineError
	public void onStateMachineError() {
	}
}

23.7. 扩展状态注解

有一个与状态相关的扩展注释。它被命名为 。您也可以仅监听更改 以获取特定更改。以下示例演示如何在有和没有属性的情况下使用 ,@OnExtendedStateChangedkey@OnExtendedStateChangedkeyspring-doc.cn

@WithStateMachine
public class Bean15 {

	@OnExtendedStateChanged
	public void anyStateChange() {
	}

	@OnExtendedStateChanged(key = "key1")
	public void key1Changed() {
	}
}

24. 使用StateMachineAccessor

StateMachine是与状态机通信的主接口。 有时,您可能需要变得更加动态和 以编程方式访问状态机的内部结构及其 嵌套计算机和区域。对于这些用例,公开了一个名为 的功能接口,该接口提供了 用于访问 individual 和 instances 的接口。StateMachineStateMachineAccessorStateMachineRegionspring-doc.cn

StateMachineFunction是一个简单的功能接口,它允许 您可以将接口应用于状态机。跟 JDK 7 创建的代码有点冗长。但是,使用 JDK 8 lambda 时, DOCE 相对不冗长。StateMachineAccessspring-doc.cn

该方法允许访问 状态机。以下示例演示如何使用它:doWithAllRegionsRegionspring-doc.cn

stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.setRelay(stateMachine));

stateMachine.getStateMachineAccessor()
	.doWithAllRegions(access -> access.setRelay(stateMachine));

该方法允许访问 状态机。以下示例演示如何使用它:doWithRegionRegionspring-doc.cn

stateMachine.getStateMachineAccessor().doWithRegion(function -> function.setRelay(stateMachine));

stateMachine.getStateMachineAccessor()
	.doWithRegion(access -> access.setRelay(stateMachine));

该方法允许访问 状态机。以下示例演示如何使用它:withAllRegionsRegionspring-doc.cn

for (StateMachineAccess<String, String> access : stateMachine.getStateMachineAccessor().withAllRegions()) {
	access.setRelay(stateMachine);
}

stateMachine.getStateMachineAccessor().withAllRegions()
	.stream().forEach(access -> access.setRelay(stateMachine));

该方法允许访问 状态机。以下示例演示如何使用它:withRegionRegionspring-doc.cn

stateMachine.getStateMachineAccessor()
	.withRegion().setRelay(stateMachine);

25. 使用StateMachineInterceptor

除了使用接口,您还可以 使用 .一个概念上的区别是,您可以使用 interceptor 拦截和停止当前状态 更改或更改其过渡逻辑。而不是实现完整的接口, 您可以使用名为 to override 的 Adapter 类 默认的 no-op 方法。StateMachineListenerStateMachineInterceptorStateMachineInterceptorAdapterspring-doc.cn

一个配方 (Persist) 和一个示例 (Persist) 与使用 拦截 器。

您可以通过 注册拦截器。的概念 拦截器是一个相对较深的内部特征,因此不是 直接通过接口公开。StateMachineAccessorStateMachinespring-doc.cn

以下示例显示了如何添加 并覆盖 selected 方法:StateMachineInterceptorspring-doc.cn

stateMachine.getStateMachineAccessor()
	.withRegion().addStateMachineInterceptor(new StateMachineInterceptor<String, String>() {

		@Override
		public Message<String> preEvent(Message<String> message, StateMachine<String, String> stateMachine) {
			return message;
		}

		@Override
		public StateContext<String, String> preTransition(StateContext<String, String> stateContext) {
			return stateContext;
		}

		@Override
		public void preStateChange(State<String, String> state, Message<String> message,
				Transition<String, String> transition, StateMachine<String, String> stateMachine,
				StateMachine<String, String> rootStateMachine) {
		}

		@Override
		public StateContext<String, String> postTransition(StateContext<String, String> stateContext) {
			return stateContext;
		}

		@Override
		public void postStateChange(State<String, String> state, Message<String> message,
				Transition<String, String> transition, StateMachine<String, String> stateMachine,
				StateMachine<String, String> rootStateMachine) {
		}

		@Override
		public Exception stateMachineError(StateMachine<String, String> stateMachine,
				Exception exception) {
			return exception;
		}
	});
有关前面示例中显示的错误处理的更多信息,请参阅状态机错误处理

26. 状态机安全

安全功能建立在 Spring Security 的功能之上。安全功能包括 当需要保护状态机的一部分时非常方便 执行和与之交互。spring-doc.cn

我们希望您对 Spring Security 相当熟悉,这意味着 我们不会详细介绍整体安全框架的工作原理。为 此信息,您应该阅读 Spring Security 参考文档 (可在此处获得)。

安全的第一级防御自然是保护事件, 这真的驱动着什么 发生在状态机中。然后,您可以定义更精细的安全设置 用于过渡和操作。这与允许员工进入架构物类似 然后授予对架构物内特定房间的访问权限,甚至 以打开和关闭特定房间中的灯。如果您信任 您的用户,事件安全性可能就是您所需要的。如果没有, 您需要应用更详细的安全性。spring-doc.cn

您可以在了解安全性中找到更多详细信息。spring-doc.cn

有关完整示例,请参阅 Security 示例。

26.1. 配置安全性

安全性的所有通用配置都在 中完成,该配置可从 获得。默认情况下,安全性处于禁用状态, 即使 Spring Security 类是 目前。以下示例说明如何启用安全性:SecurityConfigurerStateMachineConfigurationConfigurerspring-doc.cn

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

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.transitionAccessDecisionManager(null)
				.eventAccessDecisionManager(null);
	}
}

如果您绝对需要,您可以为 events 和 转换。如果您未定义决策管理器或 将它们设置为 ,默认管理器将在内部创建。AccessDecisionManagernullspring-doc.cn

26.2. 保护事件

事件安全性在全局级别由 定义。 以下示例显示如何启用事件安全性:SecurityConfigurerspring-doc.cn

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

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.event("true")
				.event("ROLE_ANONYMOUS", ComparisonType.ANY);
	}
}

在前面的配置示例中,我们使用 的表达式 ,该表达式始终计算 自。使用始终计算结果的表达式在实际应用程序中没有意义,但可以显示以下点: expression 需要返回 或 。我们还定义了一个 的属性 和 的 。有关使用属性的更多信息 和表达式,请参阅使用安全属性和表达式trueTRUETRUETRUEFALSEROLE_ANONYMOUSComparisonTypeANYspring-doc.cn

26.3. 保护过渡

您可以全局定义过渡安全性,如下例所示。spring-doc.cn

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

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.transition("true")
				.transition("ROLE_ANONYMOUS", ComparisonType.ANY);
	}
}

如果在转换本身中定义了安全性,则它会覆盖任何 全局设置安全性。以下示例显示了如何执行此操作:spring-doc.cn

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

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S0")
				.target("S1")
				.event("A")
				.secured("ROLE_ANONYMOUS", ComparisonType.ANY)
				.secured("hasTarget('S1')");
	}
}

有关使用属性和表达式的更多信息,请参阅使用安全属性和表达式spring-doc.cn

26.4. 保护操作

状态中的操作没有专用的安全定义 machine 中,但您可以使用全局方法 security 来保护操作 来自 Spring Security。这需要一个 be 定义为代理,其方法用 .以下示例显示了如何执行此操作:Action@Beanexecute@Securedspring-doc.cn

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

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true);
	}

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

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

	@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
	@Bean
	public Action<String, String> securedAction() {
		return new Action<String, String>() {

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

}

需要使用 Spring Security 启用全局方法安全性。 以下示例显示了如何执行此操作:spring-doc.cn

@Configuration
public static class Config5 {

	@Bean
	public InMemoryUserDetailsManager userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
				.username("user")
				.password("password")
				.roles("USER")
				.build();
		return new InMemoryUserDetailsManager(user);
	}
}

有关更多详细信息,请参阅 Spring Security 参考指南(在此处提供)。spring-doc.cn

26.5. 使用安全属性和表达式

通常,您可以通过以下两种方式之一定义安全属性:通过 使用安全属性和通过使用安全表达式。 属性更易于使用,但在 功能性。表达式提供了更多功能,但有点 更难使用。spring-doc.cn

26.5.1. 通用属性用法

默认情况下,events 和 转换都使用 ,这意味着您可以使用角色属性 来自 Spring Security。AccessDecisionManagerRoleVoterspring-doc.cn

对于属性,我们有三种不同的比较类型: 、 和 。这些比较类型映射到默认访问决策管理器 (分别为 、 和 )。 如果您已定义 custom ,则比较类型为 有效地丢弃,因为它仅用于创建默认管理器。ANYALLMAJORITYAffirmativeBasedUnanimousBasedConsensusBasedAccessDecisionManagerspring-doc.cn

26.5.2. 泛型表达式的用法

安全表达式必须返回 或 。TRUEFALSEspring-doc.cn

表达式根对象的基类是 .它提供了一些常见的表达式,这些表达式 在 Transition 和 Event Security 中均可用。下表 描述最常用的内置表达式:SecurityExpressionRootspring-doc.cn

表 1.常见的内置表达式
表达 描述

hasRole([role])spring-doc.cn

如果当前委托人具有指定的角色,则返回。由 default,如果提供的角色不以 开头,则为 添加。您可以通过修改 on .trueROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerspring-doc.cn

hasAnyRole([role1,role2])spring-doc.cn

如果当前委托人具有任何提供的 roles (以逗号分隔的字符串列表形式给出)。默认情况下,如果每个 提供的角色不以 开头,而是添加。您可以自定义此 通过修改 on .trueROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerspring-doc.cn

hasAuthority([authority])spring-doc.cn

如果当前委托人具有指定的授权,则返回。truespring-doc.cn

hasAnyAuthority([authority1,authority2])spring-doc.cn

如果当前委托人具有任何提供的 roles (以逗号分隔的字符串列表形式给出)。truespring-doc.cn

principalspring-doc.cn

允许直接访问表示 当前用户。spring-doc.cn

authenticationspring-doc.cn

允许直接访问获取的当前对象 从 .AuthenticationSecurityContextspring-doc.cn

permitAllspring-doc.cn

始终计算结果为 。truespring-doc.cn

denyAllspring-doc.cn

始终计算结果为 。falsespring-doc.cn

isAnonymous()spring-doc.cn

如果当前主体是匿名用户,则返回。truespring-doc.cn

isRememberMe()spring-doc.cn

如果当前委托人是 remember-me 用户,则返回。truespring-doc.cn

isAuthenticated()spring-doc.cn

如果用户不是匿名的,则返回。truespring-doc.cn

isFullyAuthenticated()spring-doc.cn

如果用户不是匿名用户或记住我用户,则返回。truespring-doc.cn

hasPermission(Object target, Object permission)spring-doc.cn

如果用户有权访问为 given 权限 — 例如 .truehasPermission(domainObject, 'read')spring-doc.cn

hasPermission(Object targetId, String targetType, Object permission)spring-doc.cn

如果用户有权访问为 given 权限 — 例如 .truehasPermission(1, 'com.example.domain.Message', 'read')spring-doc.cn

26.5.3. 事件属性

您可以使用前缀 来匹配事件 ID。例如,匹配 event 将匹配 的属性 。EVENT_AEVENT_Aspring-doc.cn

26.5.4. 事件表达式

events 的表达式根对象的基类是 .它提供对对象的访问,该对象通过事件传递。 只有一个方法,下表对此进行了说明:EventSecurityExpressionRootMessageEventSecurityExpressionRootspring-doc.cn

表 2.事件表达式
表达 描述

hasEvent(Object event)spring-doc.cn

如果事件与给定事件匹配,则返回。truespring-doc.cn

26.5.5. 过渡属性

在匹配过渡源和目标时,您可以分别使用 和 前缀。TRANSITION_SOURCE_TRANSITION_TARGET_spring-doc.cn

26.5.6. 过渡表达式

转换的表达式根对象的基类是 。它提供对对象的访问,该对象在转换更改时传递。 有两种方法,其中 表描述了:TransitionSecurityExpressionRootTransitionTransitionSecurityExpressionRootspring-doc.cn

表 3.过渡表达式
表达 描述

hasSource(Object source)spring-doc.cn

如果过渡源与给定源匹配,则返回。truespring-doc.cn

hasTarget(Object target)spring-doc.cn

如果过渡目标与给定目标匹配,则返回。truespring-doc.cn

26.6. 了解安全性

本节提供了有关安全性在 状态机。你可能真的不需要知道,但确实需要 保持透明总是更好,而不是隐藏所有的魔法 发生在幕后。spring-doc.cn

只有当 Spring Statemachine 在封闭的 garden 中,用户无法直接访问应用程序,因此可能会 在本地线程中修改 Spring Security 的保留。 如果用户控制 JVM,那么实际上就没有安全性 完全。SecurityContext

安全集成点是使用 StateMachineInterceptor 创建的,然后自动添加到 状态机(如果启用了安全性)。特定的类是 ,它拦截事件和 转换。然后,此拦截器会咨询 Spring Security 的,以确定是否可以发送事件,或者是否可以发送 transition 执行。实际上,如果决策或投票导致异常,则事件或转换将被拒绝。StateMachineSecurityInterceptorAccessDecisionManagerAccessDecisionManagerspring-doc.cn

由于 Spring Security 的工作原理,我们 每个 Secure Object 需要一个 IT 实例。这就是为什么有 是事件和过渡的不同管理器。在这种情况下,事件 和 transitions 是我们保护的不同类对象。AccessDecisionManagerspring-doc.cn

默认情况下,对于事件,投票者(、 和 )将添加到 .EventExpressionVoterEventVoterRoleVoterAccessDecisionManagerspring-doc.cn

默认情况下,对于转换,投票者 (、 和 ) 将添加到 .TransitionExpressionVoterTransitionVoterRoleVoterAccessDecisionManagerspring-doc.cn

27. 状态机错误处理

如果状态机在状态转换期间检测到内部错误 logic,它可能会引发异常。处理此异常之前 在内部,您有机会进行拦截。spring-doc.cn

通常,您可以使用来拦截错误和 下面的清单显示了一个示例:StateMachineInterceptorspring-doc.cn

StateMachine<String, String> stateMachine;

void addInterceptor() {
	stateMachine.getStateMachineAccessor()
			.doWithRegion(function ->
				function.addStateMachineInterceptor(new StateMachineInterceptorAdapter<String, String>() {
					@Override
					public Exception stateMachineError(StateMachine<String, String> stateMachine,
													   Exception exception) {
						return exception;
					}
				})
			);

}

当检测到错误时,将执行正常事件通知机制。 这允许您使用 a 或 Spring 应用程序 context 事件侦听器。有关这些内容的更多信息,请参阅侦听状态机事件StateMachineListenerspring-doc.cn

话虽如此,下面的示例显示了一个简单的侦听器:spring-doc.cn

public class ErrorStateMachineListener
		extends StateMachineListenerAdapter<String, String> {

	@Override
	public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
		// do something with error
	}
}

以下示例显示了通用检查:ApplicationListenerStateMachineEventspring-doc.cn

public class GenericApplicationEventListener
		implements ApplicationListener<StateMachineEvent> {

	@Override
	public void onApplicationEvent(StateMachineEvent event) {
		if (event instanceof OnStateMachineError) {
			// do something with error
		}
	}
}

您也可以直接定义 仅识别实例,如下例所示:ApplicationListenerStateMachineEventspring-doc.cn

public class ErrorApplicationEventListener
		implements ApplicationListener<OnStateMachineError> {

	@Override
	public void onApplicationEvent(OnStateMachineError event) {
		// do something with error
	}
}
为 transition 定义的 Action 也有自己的错误处理 逻辑。请参阅 Transition Action 错误处理

使用响应式 API 时,可能会出现 Action execution 错误 从 StateMachineEventResult 返回。拥有简单的机器 操作中的错误正在转换为 状态 。S1spring-doc.cn

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

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("SI")
				.stateEntry("S1", (context) -> {
					throw new RuntimeException("example error");
				});
	}

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

下面的测试概念显示了如何消耗可能的误差 从 StateMachineEventResult 获取。spring-doc.cn

@Autowired
private StateMachine<String, String> machine;

@Test
public void testActionEntryErrorWithEvent() throws Exception {
	StepVerifier.create(machine.startReactively()).verifyComplete();
	assertThat(machine.getState().getIds()).containsExactlyInAnyOrder("SI");

	StepVerifier.create(machine.sendEvent(Mono.just(MessageBuilder.withPayload("E1").build())))
		.consumeNextWith(result -> {
			StepVerifier.create(result.complete()).consumeErrorWith(e -> {
				assertThat(e).isInstanceOf(StateMachineException.class).cause().hasMessageContaining("example error");
			}).verify();
		})
		.verifyComplete();

	assertThat(machine.getState().getIds()).containsExactlyInAnyOrder("S1");
}
进入/退出操作中的错误不会阻止转换的发生。

28. 状态机服务

StateMachine 服务是更高级别的实现,旨在 提供更多用户级功能以简化正常运行时 操作。目前只有一个服务接口 () 存在。StateMachineServicespring-doc.cn

28.1. 使用StateMachineService

StateMachineService是用于处理正在运行的计算机的接口 并且具有 “获取” 和 “释放” 机器的简单方法。它有 一个名为 .DefaultStateMachineServicespring-doc.cn

29. 持久化状态机

传统上,状态机的实例在 running 程序。您可以通过使用 动态构建器和工厂,允许 State Machine 实例化。构建状态机的实例是一个 操作相对较重。因此,如果您需要(例如)处理 使用状态机在数据库中进行任意状态更改,您需要 找到一种更好、更快的方法来做到这一点。spring-doc.cn

persist 功能允许您保存状态机的状态 添加到外部存储库中,然后根据 serialized 状态。例如,如果您有一个数据库表保存 orders,则使用 state 更新 Order state 的成本太高了 machine 的实例。 persist 功能允许您在没有 实例化新的状态机实例。spring-doc.cn

有一个配方(请参阅 Persist)和一个示例 (请参阅 Persist),其中提供了有关 持久状态。

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

您可以改用状态机拦截器来尝试保存 序列化状态到外部存储中 state machine 内的 change 进行如果该拦截器回调失败, 您可以停止状态更改尝试,而不是以 inconsistent 状态,然后您可以手动处理此错误。有关如何使用拦截器的信息,请参见使用 StateMachineInterceptorspring-doc.cn

29.1. 使用StateMachineContext

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

StateMachineContext包含两种不同的信息包含 对于子上下文。这些通常在机器包含 正交区域。首先,上下文可以具有子上下文的列表 可以按原样使用,就好像它们存在一样。其次,您可以 包括原始上下文子级 未到位。这些子引用实际上是 保留运行多个并行区域的计算机 独立地。spring-doc.cn

Data Multi Persist 示例显示 如何持久保存并行区域。

29.2. 使用StateMachinePersister

构建并恢复状态机 如果完成的话,它总是有一点点 “黑魔法” 手动地。该界面旨在缓解这些 通过提供和方法进行操作。默认的 此接口的实现是 。StateMachineContextStateMachinePersisterpersistrestoreDefaultStateMachinePersisterspring-doc.cn

我们可以通过以下方式展示如何使用 a A 测试中的片段。我们首先创建两个类似的配置 ( 和 ) 表示状态机。请注意,我们可以构建不同的 机器以其他方式进行演示,但 this way 适用于这种情况。以下示例配置两个状态机:StateMachinePersistermachine1machine2spring-doc.cn

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

由于我们正在使用对象,因此我们可以在内存中创建一个 实现。StateMachinePersistspring-doc.cn

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

下面的清单显示了如何使用内存中示例:spring-doc.cn

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 转移到 state 中。然后我们可以持久保存它并恢复 .以下示例显示了如何执行此操作:machine1S2E1machine2spring-doc.cn

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

29.3. 使用 Redis

RepositoryStateMachinePersist(实现)支持将状态机持久化到 Redis 中。 具体实现是一个 ,它使用序列化来 将 A 持久化为 。StateMachinePersistRedisStateMachineContextRepositorykryoStateMachineContextRedisspring-doc.cn

对于 ,我们有一个与 Redis 相关的实现,它采用 a 并用作其 Context 对象。StateMachinePersisterRedisStateMachinePersisterStateMachinePersistStringspring-doc.cn

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

RedisStateMachineContextRepository需要一个让它工作。我们建议使用 a for it,如前面的示例所示。RedisConnectionFactoryJedisConnectionFactoryspring-doc.cn

29.4. 使用StateMachineRuntimePersister

StateMachineRuntimePersister是一个简单的扩展,它添加了一个接口级方法来与之关联。然后,这个拦截器是 需要在 state 更改期间持久化机器,而无需 停止和启动计算机。StateMachinePersistStateMachineInterceptorspring-doc.cn

目前,此接口有 支持的 Spring Data Repositories。这些实现是 、 、 和。JpaPersistingStateMachineInterceptorMongoDbPersistingStateMachineInterceptorRedisPersistingStateMachineInterceptorspring-doc.cn

有关详细用法,请参阅 Data Persist 示例。

30. Spring Boot 支持

自动配置模块 () 包含所有 与 Spring Boot 集成的逻辑,它为 自动配置和执行器。您所需要的只是拥有这个 Spring Statemachine 库作为启动应用程序的一部分。spring-statemachine-autoconfigurespring-doc.cn

30.1. 监控和跟踪

BootStateMachineMonitor是自动创建的,并与 状态机。 是与 Spring Boot 和端点集成的自定义实现 通过自定义 .或者,您可以禁用此自动配置 通过将密钥设置为 .Monitoring 示例显示了如何使用此自动配置。BootStateMachineMonitorStateMachineMonitorMeterRegistryStateMachineTraceRepositoryspring.statemachine.monitor.enabledfalsespring-doc.cn

30.2. 仓库配置

如果从 Classpath 中找到所需的类,则 Spring Data Repositories 实体类扫描会自动配置 以获取存储库支持spring-doc.cn

当前支持的配置包括 、 和 。您可以分别使用 、 和 属性来禁用存储库自动配置。JPARedisMongoDBspring.statemachine.data.jpa.repositories.enabledspring.statemachine.data.redis.repositories.enabledspring.statemachine.data.mongo.repositories.enabledspring-doc.cn

31. 监控状态机

您可以使用 获取有关 执行过渡和操作所花费的时间的持续时间。以下清单 演示如何实现此接口。StateMachineMonitorspring-doc.cn

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) {
	}
}

拥有实施后,您可以将其添加到 通过配置的状态机,如下例所示:StateMachineMonitorspring-doc.cn

@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();
	}
}
有关详细用法,请参阅 Monitoring 示例。

32. 使用分布式状态

分布式状态可能是 Spring 状态机。究竟什么是分布式状态?状态 在单个状态机中自然很容易理解, 但是,当需要引入共享分布式状态时 通过状态机,事情会变得有点复杂。spring-doc.cn

分布式状态功能仍是一项预览功能,并非 但被认为在此特定版本中是稳定的。我们期待这一点 功能使其成熟到第一个正式版本。

有关通用配置支持的信息,请参阅配置通用设置。有关实际使用示例,请参阅 Zookeeper 示例。spring-doc.cn

分布式状态机是通过包装实际实例的类实现的 的 . 拦截 通信并与 通过接口处理的分布式状态抽象。根据实际实现, 您还可以使用该接口序列化 ,其中包含足够的信息来重置 .DistributedStateMachineStateMachineDistributedStateMachineStateMachineStateMachineEnsembleStateMachinePersistStateMachineContextStateMachinespring-doc.cn

虽然分布式状态机是通过抽象实现的, 当前仅存在一个实现。它基于 Zookeeper。spring-doc.cn

以下示例显示如何配置基于 Zookeeper 的分布式状态 machine' 的spring-doc.cn

@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 的分布式 附录中的状态机。spring-doc.cn

32.1. 使用ZookeeperStateMachineEnsemble

ZookeeperStateMachineEnsemble本身需要两个强制设置, 的实例和 .客户端是 ,路径是实例中树的根。curatorClientbasePathCuratorFrameworkZookeeperspring-doc.cn

(可选)您可以设置 ,如果集成中不存在成员,则默认为并清除现有数据。您可以设置 如果要在 应用程序重新启动。cleanStateTRUEFALSEspring-doc.cn

(可选)您可以设置 (defaults 到 ) 保留状态更改的历史记录。这个 set 必须是 2 的幂。 通常是一个不错的默认值 价值。如果特定状态机被 大小,则会进入 Error 状态并与 ensemble 的 Ensemble 表示它已经失去了它的历史和完全重建 synchronized 状态。logSize3232spring-doc.cn

33. 测试支持

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

StateMachineTestPlanBuilder构建一个 、 它有一个方法(称为 )。该方法运行一个计划。 包含一个 Fluent Builder API,允许您添加 步骤。在这些步骤中,您可以发送事件并检查 各种条件,例如状态更改、转换和扩展状态 变量。StateMachineTestPlantest()StateMachineTestPlanBuilderspring-doc.cn

以下示例用于构建状态机:StateMachineBuilderspring-doc.cn

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();
}

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

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();

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

前面的测试示例使用以下 Hamcrest 导入:spring-doc.cn

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;
预期结果的所有可能选项都记录在 StateMachineTestPlanStepBuilder 的 Javadoc 中。

34. Eclipse 建模支持

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

在 Eclipse 向导中,您可以使用 UML 图创建新的 Papyrus 模型 语言。在此示例中,它名为 。那么你 有一个选项可以从各种图表类型中进行选择,并且您必须选择一个 .simple-machineStateMachine Diagramspring-doc.cn

我们想要创建一台具有两种状态 ( 和 ) 的机器,其中 是初始状态。然后,我们需要创建 event 来执行 transition 从 到 。在 Papyrus 中,机器看起来就像什么东西 以下示例:S1S2S1E1S1S2spring-doc.cn

简单机械

在后台,原始 UML 文件将类似于以下示例:spring-doc.cn

<?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 的会话中,它不了解如何打开一个实际的 state 图表。这是 Papyrus 插件中的一个已知问题,并且有一个简单的 解决方法。在 Papyrus 透视图中,您可以看到 您的模型。双击 Diagram StateMachine Diagram,它 指示 Eclipse 在其适当的 Papyrus 中打开此特定模型 modeling 插件。.di.notation.uml

34.1. 使用UmlStateMachineModelFactory

在项目中放置 UML 文件后,您可以将其导入到 配置,其中 与模型关联。 是一家懂得 处理 Eclipse Papyrus_generated UML 结构。源 UML 文件可以 可以作为 Spring 或普通位置字符串给出。 以下示例说明如何创建 的实例:StateMachineModelConfigurerStateMachineModelFactoryUmlStateMachineModelFactoryResourceUmlStateMachineModelFactoryspring-doc.cn

@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 Statemachine 与 guard 和 操作,这些操作被定义为 bean。这些需要挂接到 UML 中 通过其内部建模结构。以下部分显示 如何在 UML 定义中定义自定义的 Bean 引用。 请注意,也可以手动注册特定方法 而不将它们定义为 bean。spring-doc.cn

如果创建为 Bean,则会自动连接其以查找已注册的操作,并且 警卫。您还可以手动定义 ,然后使用该 API 查找这些 组件。工厂还具有 registerActionregisterGuard 方法,您可以使用它们来注册这些组件。了解更多 有关此内容,请参阅使用 StateMachineComponentResolverUmlStateMachineModelFactoryResourceLoaderStateMachineComponentResolverspring-doc.cn

UML 模型在实现(如 Spring Statemachine 本身。Spring Statemachine 留下了如何实现许多功能,并且 功能,直到实际实现。以下部分 通过 Spring Statemachine 如何基于 Eclipse Papyrus 插件。spring-doc.cn

34.1.1. 使用StateMachineComponentResolver

下一个示例展示了如何使用 a ,它分别注册 和 函数。请注意,这些组件 不会创建为 Bean。下面的清单显示了该示例:UmlStateMachineModelFactoryStateMachineComponentResolvermyActionmyGuardspring-doc.cn

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

34.2. 创建模型

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

纸莎草纸 GS 1

您可以先创建一个新模型并为其命名,如下图所示:spring-doc.cn

GS 2 纸莎草纸

然后你需要选择 StateMachine Diagram,如下所示:spring-doc.cn

GS 3 纸莎草纸

您最终会得到一个空的状态机。spring-doc.cn

在前面的图像中,您应该创建了一个名为 . 您应该得到三个文件:、 、 和 .然后,您可以在任何其他 Eclipse 实例。此外,您还可以导入到 Spring Statemachine 的 Statemachine 中。modelmodel.dimodel.notationmodel.umlmodel.umlspring-doc.cn

34.3. 定义 State

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

GS 4 纸莎草纸

在上图中,我们添加了一个根元素和一个初始状态 ()。然后我们画了一个过渡 来表示这是一个初始状态。S1S1spring-doc.cn

GS 5 纸莎草纸

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

34.4. 定义事件

要将事件与过渡关联,您需要创建一个 Signal (在本例中)。为此,请选择 RootElement → New Child → Signal。 下图显示了结果:E1spring-doc.cn

纸莎草纸 GS 6

然后,您需要使用新 Signal 创建 SignalEvent。 为此,请选择 RootElement → New Child → SignalEvent。 下图显示了结果:E1spring-doc.cn

纸莎草纸 GS 7

现在,您已经定义了 ,您可以使用它来关联 带有 transition 的 Trigger。有关更多信息,请参阅定义过渡SignalEventspring-doc.cn

34.4.1. 延迟事件

您可以推迟事件,以便在更合适的时间处理它们。在 UML,这是从状态本身完成的。选择任意状态,创建一个 new 触发器,然后选择 SignalEvent,该 匹配要延迟的 Signal。spring-doc.cn

34.5. 定义过渡

您可以通过在 源状态和目标状态。在前面的图像中,我们有状态 和 以及 两者之间的匿名过渡。我们希望将 event 与该转换相关联。我们选择一个过渡,创建一个新的 trigger,并为此定义 SignalEventE1,如下图所示:S1S2E1spring-doc.cn

GS 8 纸莎草纸

这为您提供了如下图所示的排列方式:spring-doc.cn

GS 9 纸莎草纸
如果省略过渡的 SignalEvent,它将变为 匿名转换。

34.6. 定义计时器

转换也可以基于定时事件发生。Spring Statemachine 支持两种类型的计时器,一种在 background 和 Ones 在 state 为 进入。spring-doc.cn

要将新的 TimeEvent 子项添加到 Model Explorer,请将 When 修改为 表达式定义为 LiteralInteger。它的值(以毫秒为单位)成为计时器。 Leave Is Relative false 使计时器连续触发。spring-doc.cn

GS 10 纸莎草纸

要定义一个在进入状态时触发的基于定时的事件,该过程恰好是 与前面描述相同,但将 Is Relative (相对) 设置为 true。下图 显示结果:spring-doc.cn

GS 11 纸莎草纸

然后,用户可以选择这些定时事件之一,而不是 signal 事件。spring-doc.cn

34.7. 定义一个选择

通过将一个传入过渡绘制到 CHOICE 状态并绘制从该状态到目标的多个传出过渡 国家。我们的配置模型允许您定义 if/elseif/else 结构。但是,对于 UML,我们需要使用 用于传出转换的单个 Guard。StateConfigurerspring-doc.cn

您必须确保为 transitions 定义的守卫不会重叠,以便 无论发生什么,在任何给定的 guard 中,只有一个守卫的计算结果为 TRUE 时间。这为 choice 分支提供了精确且可预测的结果 评估。此外,我们建议保留一个没有守卫的 transition ,以便保证至少一个过渡路径。 下图显示了使用三个分支进行选择的结果:spring-doc.cn

纸莎草纸 GS 16
Junction 的工作方式类似,只是它允许多个传入 转换。因此,与 Choice 相比,它的行为纯粹是 学术。选择 out-direction transition 的实际 logic 是完全相同的。

34.8. 定义 Junction

请参阅 定义选择项。spring-doc.cn

34.9. 定义 Entry 和 Exit Points

您可以使用 EntryPoint 和 ExitPoint 创建受控的进入和退出 与具有子状态的 state 一起。在下面的状态图中,事件 和 通过进入和退出状态 具有正常状态行为,而正常状态行为通过进入初始状态 发生。E1E2S2S21spring-doc.cn

Using 事件将机器带入 EntryPoint,然后 导致 NOT ACTIVE INITIAL state 在任何时候。 同样,带有事件的 ExitPoint 控制特定的退出 into state 中,而正常的 exit behavior from 将采用 machine 进入状态 。在 state 上时,您可以选择 事件并使机器进入状态或 , 分别。下图显示了结果:E3ENTRYS22S21EXITE4S4S2S3S22E4E2S3S4spring-doc.cn

纸莎草纸 GS 17
如果 state 被定义为子机引用,并且你需要使用入口点和出口点, 您必须在外部定义一个 ConnectionPointReference,并使用 其 Entry 和 Exit 引用设置为指向正确的 Entry 或 Exit 点 在 Submachine 引用中。只有在那之后,才有可能 定位从外部正确链接到内部的过渡 Sub-machine 引用。使用 ConnectionPointReference,您可能需要 从 Properties → Advanced → UML →查找这些设置 进入/退出。UML 规范允许您定义多个入口和出口。然而 对于状态机,只允许一个。

34.10. 定义历史状态

在处理历史状态时,有三个不同的概念在起作用。 UML 定义了 Deep History 和 Shallow History。默认历史记录 当历史状态尚未知时,状态开始发挥作用。这些是 在以下各节中表示。spring-doc.cn

34.10.1. 浅层历史

在下图中,选择了 Shallow History 并在其中定义了一个过渡:spring-doc.cn

纸莎草纸 GS 18

34.10.2. 深层历史

Deep History 用于具有其他深层嵌套状态的状态, 从而有机会保存整个嵌套的 state 结构。 下图显示了使用 Deep History 的定义:spring-doc.cn

GS 19 纸莎草纸

34.10.3. 默认历史记录

如果 Transition 在 该状态在达到其 final 状态,则有一个强制选项 到特定子状态的过渡,使用默认的 history 机制。为此,您必须定义一个过渡 设置为此默认状态。这是从 到 的过渡。SHS22spring-doc.cn

在下图中,如果 state 具有 从未活跃过,因为它的历史从未被记录下来。如果 state 已处于活动状态,则选择 or 。S22S2S2S20S21spring-doc.cn

纸莎草纸 GS 20

34.11. 定义 fork 和 join

Fork 和 Join 在 Papyrus 中都表示为条形。如图所示 在下一个图像中,您需要绘制一个从 Into 状态的传出过渡,以具有正交区域。 则相反,其中 从 incoming transitions 中收集 join 状态。FORKS2JOINspring-doc.cn

纸莎草纸 GS 21

34.12. 定义操作

您可以使用行为来关联 Swtate 进入和退出操作。 有关此内容的更多信息,请参见定义 Bean 引用spring-doc.cn

34.12.1. 使用初始操作

定义了初始操作(如配置操作中所示) 在 UML 中,通过在转换中添加一个从初始状态引出的操作 marker 添加到实际状态中。然后,当状态 machine 已启动。spring-doc.cn

34.13. 定义守卫

您可以通过首先添加 Constraint,然后定义 其 Specification 指定为 OpaqueExpression,其工作方式相同 定义为定义 Bean 引用spring-doc.cn

34.14. 定义 Bean 引用

当您需要在任何 UML 效果中进行 bean 引用时, action 或 guard 执行此操作,您可以使用 或 来执行此操作,其中定义的语言需要 be 和语言体 msut 具有 bean 引用 ID。FunctionBehaviorOpaqueBehaviorbeanspring-doc.cn

34.15. 定义 SPEL 引用

当你需要在 任何 UML 效果、操作或守卫,都可以通过使用 或 来实现,其中定义的语言需要 be 并且语言主体必须是 SPEL 表达式。FunctionBehaviorOpaqueBehaviorspelspring-doc.cn

34.16. 使用 Sub-Machine 引用

通常,当您使用子状态时,您会将它们绘制到 state 中 图表本身。图表可能会变得过于复杂和庞大,以至于无法 follow,因此我们也支持将子 state 定义为 state machine 参考。spring-doc.cn

要创建子计算机引用,必须首先创建一个新图表并为其命名 (例如,SubStateMachine Diagram)。下图显示了要使用的菜单选项:spring-doc.cn

纸莎草纸 GS 12

为新图表提供您需要的设计。 下图显示了一个简单的设计作为示例:spring-doc.cn

GS 13 纸莎草纸

从要链接的状态(在本例中为 m state )中,单击字段并选择链接的计算机(在我们的示例中为 )。S2SubmachineSubStateMachinespring-doc.cn

纸莎草纸 GS 14

最后,在下图中,您可以看到 state 被链接为 子状态。S2SubStateMachinespring-doc.cn

纸莎草纸 GS 15

34.17. 使用机器导入

也可以在 uml 文件可以引用其他模型的地方使用导入功能。spring-doc.cn

纸莎草纸 GS 22

在其中可以使用其他资源或位置 以定义引用模型文件。UmlStateMachineModelFactoryspring-doc.cn

@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 模型中文件之间的链接需要是相对的,因为 否则,当模型文件从 classpath 添加到临时目录,以便 Eclipse 解析类可以 阅读这些。

35. 存储库支持

本节包含与使用 'Spring Data Repositories 的 Spring Statemachine 中。spring-doc.cn

35.1. 存储库配置

您可以将机器配置保留在外部 storage,可以从中按需加载它,而不是创建静态 配置。这 集成通过 Spring Data Repository 抽象工作。spring-doc.cn

我们创建了一个特殊的实现 叫。它可以使用 base 存储库接口(、 、 和 )和基本实体 接口 (、 、 和 )。StateMachineModelFactoryRepositoryStateMachineModelFactoryStateRepositoryTransitionRepositoryActionRepositoryGuardRepositoryRepositoryStateRepositoryTransitionRepositoryActionRepositoryGuardspring-doc.cn

由于实体和存储库在 Spring Data 中的工作方式, 从用户的角度来看,读取访问可以按原样完全抽象化 完成 。没有必要 了解存储库使用的实际映射实体类。 写入存储库始终依赖于使用实际的 特定于存储库的实体类。从计算机配置点 的观点,我们不需要知道这些,这意味着我们不需要知道 实际实现是 JPA、Redis 还是其他任何东西 Spring Data 支持的。使用实际的存储库相关 实体类在手动尝试编写新的 状态或转换到支持的存储库中。RepositoryStateMachineModelFactoryspring-doc.cn

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

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

SM 存储库 simplemachine
图 1.简单机器
sm 存储库 simplesubmachine
图 2.SimpleSubMachine
SM 存储库 ShowcaseMachine
图 3.Showcase机器

35.1.1. 日JPA

JPA 的实际存储库实现是 、 和 ,它们由 实体类 、 、 和 。JpaStateRepositoryJpaTransitionRepositoryJpaActionRepositoryJpaGuardRepositoryJpaRepositoryStateJpaRepositoryTransitionJpaRepositoryActionJpaRepositoryGuardspring-doc.cn

不幸的是,版本 '1.2.8' 不得不对 JPA 的实体进行更改 model 的 model 来使用。以前,生成的表名 始终具有从实体类派生的前缀 名字。由于这会导致数据库强加的中断问题 对数据库对象长度的限制,所有实体类都有 spesific definitions 强制使用表名。例如,现在是 'STATE' — 依此类推,与其他 ntity 类。JPA_REPOSITORY_JPA_REPOSITORY_STATE

显示了手动更新 JPA 状态和转换的通用方法 在下面的示例中(相当于 SimpleMachine 中所示的机器):spring-doc.cn

@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 中所示的计算机。spring-doc.cn

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

首先,您必须访问所有存储库。 以下示例显示了如何执行此操作:spring-doc.cn

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;

@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;

其次,你可以创建动作和守卫。 以下示例显示了如何执行此操作:spring-doc.cn

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);

第三,您必须创建状态。 以下示例显示了如何执行此操作:spring-doc.cn

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);

第四,也是最后一点,您必须创建过渡。 以下示例显示了如何执行此操作:spring-doc.cn

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);

您可以在此处找到完整的示例。此示例还演示了如何 从具有 实体类的定义。spring-doc.cn

35.1.2. Redis

Redis 实例的实际存储库实现是 、 和 ,它们由 实体类 、 、 和 。RedisStateRepositoryRedisTransitionRepositoryRedisActionRepositoryRedisGuardRepositoryRedisRepositoryStateRedisRepositoryTransitionRedisRepositoryActionRedisRepositoryGuardspring-doc.cn

下一个示例显示了手动更新 Redis 状态和过渡的通用方法。 这相当于 SimpleMachine 中所示的机器。spring-doc.cn

@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 中显示的 machine:spring-doc.cn

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

35.1.3. MongoDB 数据库

MongoDB 实例的实际存储库实现是 、 和 ,它们由 实体类 、 、 和 。MongoDbStateRepositoryMongoDbTransitionRepositoryMongoDbActionRepositoryMongoDbGuardRepositoryMongoDbRepositoryStateMongoDbRepositoryTransitionMongoDbRepositoryActionMongoDbRepositoryGuardspring-doc.cn

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

@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 中所示的计算机。spring-doc.cn

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

35.2. 存储库持久性

除了将机器配置(如仓库配置中所示)存储在外部仓库中外,您还可以 将计算机持久保存到存储库中。spring-doc.cn

该接口是一个中央接入点,它 与机器持久互,并由 Entity 类提供支持。StateMachineRepositoryRepositoryStateMachinespring-doc.cn

35.2.1. 日式分析

JPA 的实际存储库实现是 ,它由 entity 类 提供支持。JpaStateMachineRepositoryJpaRepositoryStateMachinespring-doc.cn

以下示例显示了为 JPA 保留计算机的通用方法:spring-doc.cn

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

35.2.2. Redis 餐厅

Redis 的实际存储库实现是 ,它由 entity 类 提供支持。RedisStateMachineRepositoryRedisRepositoryStateMachinespring-doc.cn

以下示例显示了为 Redis 保留计算机的通用方法:spring-doc.cn

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

35.2.3. MongoDB 数据库

MongoDB 的实际存储库实现是 ,它由 entity class 提供支持。MongoDbStateMachineRepositoryMongoDbRepositoryStateMachinespring-doc.cn

以下示例显示了为 MongoDB 持久保存计算机的通用方法:spring-doc.cn

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