使用 Spring Statemachine
参考文档的这一部分介绍了核心功能 Spring Statemachine 提供给任何基于 Spring 的应用程序。
它包括以下主题:
-
Statemachine Configuration 描述了通用配置支持。
-
状态机 ID 描述了机器 ID 的使用。
-
状态机工厂 描述了通用的状态机工厂支持。
-
使用 Scopes 介绍了范围支持。
-
Using Actions 介绍了支持的操作。
-
使用 Guards 描述了 Guard 支持。
-
使用扩展状态 介绍了扩展状态支持。
-
使用
StateContext
描述了状态上下文支持。 -
触发过渡 描述了触发器的使用。
-
侦听状态机事件 介绍了状态机侦听器的使用。
-
上下文集成描述了通用的 Spring 应用程序上下文支持。
-
使用
StateMachineAccessor
介绍了状态机内部访问器支持。 -
使用
StateMachineInterceptor
描述了状态机错误处理支持。 -
State Machine Security 描述了 state machine security 支持。
-
状态机错误处理描述了状态机拦截器支持。
-
状态机服务描述了状态机服务支持。
-
持久化状态机 描述了状态机持久化支持。
-
Spring Boot Support 描述了 Spring Boot 支持。
-
监控状态机 描述了监控和跟踪支持。
-
使用分布式状态 介绍了分布式状态机支持。
-
Testing Support 描述了状态机测试支持。
-
Eclipse 建模支持描述了状态机 UML 建模支持。
-
存储库支持 描述了状态机存储库配置支持。
12. 状态机配置
使用状态机时的常见任务之一是设计其 运行时配置。本章重点介绍 Spring Statemachine 的配置以及它如何利用 Spring 的轻量级 IoC 容器简化应用程序内部结构,使其更加 管理。
本节中的配置示例功能不完整。那是 您始终需要同时定义 State 和 transition。 否则,状态机配置将格式错误。我们有 通过保留其他需要的部分,简单地使代码片段不那么冗长 外。 |
12.1. 使用 Commentsenable
我们使用两个熟悉的 Spring 启用器注释来简化配置:和 .
这些注释在放置在类中时,启用
状态机所需的一些基本功能。@EnableStateMachine
@EnableStateMachineFactory
@Configuration
当您需要配置来创建
的实例。通常,类扩展适配器
( 或 ),其中
允许您覆盖配置回调方法。我们自动
检测是否使用这些适配器类并修改运行时配置
逻辑。@EnableStateMachine
StateMachine
@Configuration
EnumStateMachineConfigurerAdapter
StateMachineConfigurerAdapter
当您需要配置来创建
的实例。@EnableStateMachineFactory
StateMachineFactory
以下部分显示了这些用法示例。 |
12.2. 配置 State
在本指南的后面部分,我们将介绍更复杂的配置示例,但是
我们首先从简单的事情开始。对于大多数简单的状态
machine 中,您可以使用和定义
可能的状态,然后选择 Initial (初始) 和 Optional End (可选结束状态)。EnumStateMachineConfigurerAdapter
@Configuration
@EnableStateMachine
public class Config1Enums
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
}
您还可以使用字符串而不是枚举作为状态和
事件,如下一个示例所示。最
的配置示例 ues 枚举,但是,一般来说,
您可以交换字符串和枚举。StateMachineConfigurerAdapter
@Configuration
@EnableStateMachine
public class Config1Strings
extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("S1")
.end("SF")
.states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4")));
}
}
使用枚举会带来一组更安全的状态和事件类型,但 将可能的组合限制为编译时。字符串没有 this 限制,并允许使用更动态的方式来构建状态 机器配置,但不允许相同级别的安全。 |
12.3. 配置分层状态
您可以使用多个调用来定义分层状态,其中 您可以使用它来指示这些
特定状态是其他状态的子状态。
以下示例显示了如何执行此操作:withStates()
parent()
@Configuration
@EnableStateMachine
public class Config2
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.state(States.S1)
.and()
.withStates()
.parent(States.S1)
.initial(States.S2)
.state(States.S2);
}
}
12.4. 配置区域
没有特殊的配置方法可以标记 states 作为正交 state 的一部分。简单来说,正交 当同一分层状态机具有多个 set 时创建 state 的状态,每个状态都有一个初始状态。因为单个状态 machine 只能有一个初始状态,多个初始状态必须 表示一个特定的 state 必须有多个独立的 Region。 以下示例显示如何定义区域:
@Configuration
@EnableStateMachine
public class Config10
extends EnumStateMachineConfigurerAdapter<States2, Events> {
@Override
public void configure(StateMachineStateConfigurer<States2, Events> states)
throws Exception {
states
.withStates()
.initial(States2.S1)
.state(States2.S2)
.and()
.withStates()
.parent(States2.S2)
.initial(States2.S2I)
.state(States2.S21)
.end(States2.S2F)
.and()
.withStates()
.parent(States2.S2)
.initial(States2.S3I)
.state(States2.S31)
.end(States2.S3F);
}
}
当保留具有区域或通常
依靠任何功能来重置计算机,您可能需要
拥有区域的专用 ID。默认情况下,此 ID
是生成的 UUID。如下例所示,具有
一个调用的方法,用于设置区域的 ID:StateConfigurer
region(String id)
@Configuration
@EnableStateMachine
public class Config10RegionId
extends EnumStateMachineConfigurerAdapter<States2, Events> {
@Override
public void configure(StateMachineStateConfigurer<States2, Events> states)
throws Exception {
states
.withStates()
.initial(States2.S1)
.state(States2.S2)
.and()
.withStates()
.parent(States2.S2)
.region("R1")
.initial(States2.S2I)
.state(States2.S21)
.end(States2.S2F)
.and()
.withStates()
.parent(States2.S2)
.region("R2")
.initial(States2.S3I)
.state(States2.S31)
.end(States2.S3F);
}
}
12.5. 配置过渡
我们支持三种不同类型的过渡:、 和 。转换由 signal 触发
(这是发送到状态机的事件)或定时器。
以下示例显示如何定义所有三种类型的过渡:external
internal
local
@Configuration
@EnableStateMachine
public class Config3
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1).target(States.S2)
.event(Events.E1)
.and()
.withInternal()
.source(States.S2)
.event(Events.E2)
.and()
.withLocal()
.source(States.S2).target(States.S3)
.event(Events.E3);
}
}
12.6. 配置 Guard
你可以使用 guard 来保护 state transitions。您可以使用该界面
执行方法有权访问 .
以下示例显示了如何执行此操作:Guard
StateContext
@Configuration
@EnableStateMachine
public class Config4
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1).target(States.S2)
.event(Events.E1)
.guard(guard())
.and()
.withExternal()
.source(States.S2).target(States.S3)
.event(Events.E2)
.guardExpression("true");
}
@Bean
public Guard<States, Events> guard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
return true;
}
};
}
}
在前面的示例中,我们使用了两种不同类型的 guard 配置。首先,我们
创建了一个 Simple as bean 并将其附加到
states 和 .Guard
S1
S2
其次,我们使用 SPeL 表达式作为守卫来表示
expression 必须返回一个值。在幕后,这个
基于表达式的守卫是一个 .我们将其附加到
状态和 之间的过渡。两个守卫
始终计算为 。BOOLEAN
SpelExpressionGuard
S2
S3
true
12.7. 配置操作
您可以定义要使用过渡和状态执行的操作。 操作始终作为 源自触发器。以下示例说明如何定义操作:
@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
从 到 的过渡。
以下示例演示如何多次使用操作:Action
action
S1
S2
@Configuration
@EnableStateMachine
public class Config52
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1, action())
.state(States.S1, action(), null)
.state(States.S2, null, action())
.state(States.S2, action())
.state(States.S3, action(), action());
}
@Bean
public Action<States, Events> action() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// do something
}
};
}
}
通常,您不会为不同的
阶段,但我们在这里这样做是为了避免在代码中产生太多干扰
片段。Action |
在前面的示例中,single 由 bean named 和 associated
其中状态 、 和 。我们需要澄清一下这里发生了什么:Action
action
S1
S2
S3
-
我们为初始状态 定义了一个操作。
S1
-
我们为 state 定义了一个 entry 操作,并将 exit 操作留空。
S1
-
我们为 state 定义了一个 exit 操作,并将 entry 操作留空。
S2
-
我们为状态 定义了一个状态操作。
S2
-
我们为 state 定义了 entry 和 exit 动作。
S3
-
请注意,state 与 and 函数一起使用两次。仅当您想要定义 entry 或 exit 时,才需要执行此操作 具有初始状态的 action。
S1
initial()
state()
使用函数定义操作仅运行特定的
action 来触发。此操作
是仅运行一次的初始化操作。定义的操作
如果状态机转换回来,则运行 with
以及 forward 在初始状态和非初始状态之间。initial() state() |
12.7.1. 状态操作
与 entry 和 exit 相比,状态操作的运行方式不同 操作,因为执行发生在进入状态之后 如果 state exit 发生在特定操作之前,则可以取消 已完成。
State action 使用正常的响应式 flow 执行,方法是订阅
reactor 的默认并行调度器。这意味着,无论您在
操作,您需要能够捕获,或者更一般地说,
定期检查是否中断。InterruptedException
Thread
以下示例显示了使用 default 的典型配置,其中
当 state 为 complete 时,将立即取消正在运行的任务:IMMEDIATE_CANCEL
@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.stateDoActionPolicy(StateDoActionPolicy.IMMEDIATE_CANCEL);
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S2", context -> {})
.state("S3");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1")
.and()
.withExternal()
.source("S2")
.target("S3")
.event("E2");
}
}
您可以将策略设置为与全局超时一起
对于每台计算机。这会将状态行为更改为 await action completion
在请求取消之前。以下示例显示了如何执行此操作:TIMEOUT_CANCEL
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
.stateDoActionPolicyTimeout(10, TimeUnit.SECONDS);
}
如果直接将机器带入状态,以便事件标头
可用于特定操作,您还可以使用专用的
event 标头设置特定超时(在 中定义)。
您可以将 reserved 标头值用于此目的。以下示例显示了如何执行此操作:Event
millis
StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT
@Autowired
StateMachine<String, String> stateMachine;
void sendEventUsingTimeout() {
stateMachine
.sendEvent(Mono.just(MessageBuilder
.withPayload("E1")
.setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
.build()))
.subscribe();
}
12.7.2. Transition Action 错误处理
您始终可以手动捕获异常。但是,使用
transitions,您可以定义一个 error 操作,该操作在
异常。然后,该异常可从传递给该操作的 a 中获得。以下示例演示如何创建 state
处理异常:StateContext
@Configuration
@EnableStateMachine
public class Config53
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1)
.target(States.S2)
.event(Events.E1)
.action(action(), errorAction());
}
@Bean
public Action<States, Events> action() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
throw new RuntimeException("MyError");
}
};
}
@Bean
public Action<States, Events> errorAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// RuntimeException("MyError") added to context
Exception exception = context.getException();
exception.getMessage();
}
};
}
}
如果需要,您可以为每个操作手动创建类似的 logic。 以下示例显示了如何执行此操作:
@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 用于进入状态和退出状态。
对于这些情况,具有名为 、 和 的 方法 。这些方法将操作与 normal (non-error) 一起定义。
以下示例演示如何使用所有三种方法:StateConfigurer
stateEntry
stateDo
stateExit
error
action
@Configuration
@EnableStateMachine
public class Config55
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.stateEntry(States.S2, action(), errorAction())
.stateDo(States.S2, action(), errorAction())
.stateExit(States.S2, action(), errorAction())
.state(States.S3);
}
@Bean
public Action<States, Events> action() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
throw new RuntimeException("MyError");
}
};
}
@Bean
public Action<States, Events> errorAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// RuntimeException("MyError") added to context
Exception exception = context.getException();
exception.getMessage();
}
};
}
}
12.8. 配置伪状态
伪 state 配置通常是通过配置 state 和 转换。伪状态会自动作为 国家。
12.8.1. 初始状态
您可以使用该方法将特定状态标记为初始状态。例如,此初始操作适用于初始化
扩展状态变量。以下示例演示如何使用该方法:initial()
initial()
@Configuration
@EnableStateMachine
public class Config11
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1, initialAction())
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
@Bean
public Action<States, Events> initialAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
// do something initially
}
};
}
}
12.8.2. 终止状态
您可以使用该方法将特定状态标记为结束状态。
您最多可以为每个子计算机或区域执行此操作一次。
以下示例演示如何使用该方法:end()
end()
@Configuration
@EnableStateMachine
public class Config1Enums
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
}
12.8.3. 状态历史
您可以为每个单独的状态机定义一次状态历史记录。
您需要选择其状态标识符并设置 或 。以下示例使用 :History.SHALLOW
History.DEEP
History.SHALLOW
@Configuration
@EnableStateMachine
public class Config12
extends EnumStateMachineConfigurerAdapter<States3, Events> {
@Override
public void configure(StateMachineStateConfigurer<States3, Events> states)
throws Exception {
states
.withStates()
.initial(States3.S1)
.state(States3.S2)
.and()
.withStates()
.parent(States3.S2)
.initial(States3.S2I)
.state(States3.S21)
.state(States3.S22)
.history(States3.SH, History.SHALLOW);
}
@Override
public void configure(StateMachineTransitionConfigurer<States3, Events> transitions)
throws Exception {
transitions
.withHistory()
.source(States3.SH)
.target(States3.S22);
}
}
此外,如前面的示例所示,您可以选择定义默认的 在同一台机器中从历史状态过渡到状态顶点。 此过渡是默认进行的,例如,如果计算机具有 从未被输入,因此,将没有历史记录可用。如果默认的 state transition 未定义,则正常进入 Region 为 做。如果计算机的历史记录为 最终状态。
12.8.4. Choice 状态
需要在 state 和 transition to work 中定义 choice
适当地。您可以使用该方法将特定状态标记为选择状态。当转换
配置。choice()
您可以使用 配置过渡,其中定义了源
state 和一个结构,它相当于普通的 .使用 和 ,你可以指定一个守卫
就像您将使用带子句的条件一样。withChoice()
first/then/last
if/elseif/else
first
then
if/elseif
过渡需要能够存在,因此您必须确保使用 .
否则,配置格式不正确。以下示例显示了如何定义
a choice 状态:last
@Configuration
@EnableStateMachine
public class Config13
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.SI)
.choice(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withChoice()
.source(States.S1)
.first(States.S2, s2Guard())
.then(States.S3, s3Guard())
.last(States.S4);
}
@Bean
public Guard<States, Events> s2Guard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
return false;
}
};
}
@Bean
public Guard<States, Events> s3Guard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
return true;
}
};
}
}
操作可以在 Nm 的传入和传出过渡 choice 伪状态。如以下示例所示,一个虚拟 lambda action 的 API API 的 API 的 Lambda 操作(其中它还 定义一个 error 操作):
@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()
您可以使用定义源的位置来配置过渡
state 和一个结构(相当于 normal )来实现。使用 和 ,可以将守卫指定为
您将使用带子句的 Condition。withJunction()
first/then/last
if/elseif/else
first
then
if/elseif
过渡需要能够存在,因此您必须确保使用 .
否则,配置格式不正确。
以下示例使用联结:last
@Configuration
@EnableStateMachine
public class Config20
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.SI)
.junction(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withJunction()
.source(States.S1)
.first(States.S2, s2Guard())
.then(States.S3, s3Guard())
.last(States.S4);
}
@Bean
public Guard<States, Events> s2Guard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
return false;
}
};
}
@Bean
public Guard<States, Events> s3Guard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
return true;
}
};
}
}
选择和交汇点之间的区别纯粹是学术性的,因为两者都是
使用 Structures 实现。然而,从理论上讲,基于
在 UML 建模中,只允许一个传入转换,而允许多个传入转换。在代码级别,
功能几乎相同。first/then/last choice junction |
12.8.6. 分叉状态
您必须在 state 和 transitions 中定义 fork 才能正常工作
适当地。您可以使用该方法将特定状态标记为选择状态。当转换
配置。fork()
目标状态需要是 super 状态或 immediate 状态中的 地区。使用超级状态作为目标会将所有区域置于 初始状态。以单个状态为目标,入口控制更严格 到区域。以下示例使用 fork:
@Configuration
@EnableStateMachine
public class Config14
extends EnumStateMachineConfigurerAdapter<States2, Events> {
@Override
public void configure(StateMachineStateConfigurer<States2, Events> states)
throws Exception {
states
.withStates()
.initial(States2.S1)
.fork(States2.S2)
.state(States2.S3)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S2I)
.state(States2.S21)
.state(States2.S22)
.end(States2.S2F)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S3I)
.state(States2.S31)
.state(States2.S32)
.end(States2.S3F);
}
@Override
public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
throws Exception {
transitions
.withFork()
.source(States2.S2)
.target(States2.S22)
.target(States2.S32);
}
}
12.8.7. Join State
您必须在 state 和 transition 中定义一个 join 才能正常工作
适当地。您可以使用该方法将特定状态标记为选择状态。此状态不需要匹配源状态或
target 状态。join()
您可以选择当所有源状态时转换转到的目标状态 已加入。如果您使用 State Hosting Regions 作为源,则 区域的状态用作联接。否则,您可以选择任何 州。以下示例使用 join:
@Configuration
@EnableStateMachine
public class Config15
extends EnumStateMachineConfigurerAdapter<States2, Events> {
@Override
public void configure(StateMachineStateConfigurer<States2, Events> states)
throws Exception {
states
.withStates()
.initial(States2.S1)
.state(States2.S3)
.join(States2.S4)
.state(States2.S5)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S2I)
.state(States2.S21)
.state(States2.S22)
.end(States2.S2F)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S3I)
.state(States2.S31)
.state(States2.S32)
.end(States2.S3F);
}
@Override
public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
throws Exception {
transitions
.withJoin()
.source(States2.S2F)
.source(States2.S3F)
.target(States2.S4)
.and()
.withExternal()
.source(States2.S4)
.target(States2.S5);
}
}
您还可以让多个过渡源自
join 状态。在这种情况下,我们建议你使用 guard 并定义你的 guard
使得在任何给定时间只有一个守卫计算。否则
过渡行为是不可预测的。这在以下示例中显示,其中 guard
检查 extended state 是否有变量:TRUE
@Configuration
@EnableStateMachine
public class Config22
extends EnumStateMachineConfigurerAdapter<States2, Events> {
@Override
public void configure(StateMachineStateConfigurer<States2, Events> states)
throws Exception {
states
.withStates()
.initial(States2.S1)
.state(States2.S3)
.join(States2.S4)
.state(States2.S5)
.end(States2.SF)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S2I)
.state(States2.S21)
.state(States2.S22)
.end(States2.S2F)
.and()
.withStates()
.parent(States2.S3)
.initial(States2.S3I)
.state(States2.S31)
.state(States2.S32)
.end(States2.S3F);
}
@Override
public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
throws Exception {
transitions
.withJoin()
.source(States2.S2F)
.source(States2.S3F)
.target(States2.S4)
.and()
.withExternal()
.source(States2.S4)
.target(States2.S5)
.guardExpression("!extendedState.variables.isEmpty()")
.and()
.withExternal()
.source(States2.S4)
.target(States2.SF)
.guardExpression("extendedState.variables.isEmpty()");
}
}
12.8.8. 退出点和入口点状态
您可以使用退出点和入场点来执行更受控的退出和进入操作
从 和 进入 Submachine。
以下示例使用 and 方法定义入口点:withEntry
withExit
@Configuration
@EnableStateMachine
static class Config21 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("S1")
.state("S2")
.state("S3")
.and()
.withStates()
.parent("S2")
.initial("S21")
.entry("S2ENTRY")
.exit("S2EXIT")
.state("S22");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("S1").target("S2")
.event("E1")
.and()
.withExternal()
.source("S1").target("S2ENTRY")
.event("ENTRY")
.and()
.withExternal()
.source("S22").target("S2EXIT")
.event("EXIT")
.and()
.withEntry()
.source("S2ENTRY").target("S22")
.and()
.withExit()
.source("S2EXIT").target("S3");
}
}
如上所示,您需要将特定状态标记为 being 和 states。然后,创建到这些状态的正常过渡
并指定 和 ,其中这些状态
分别退出和进入。exit
entry
withExit()
withEntry()
12.9. 配置通用设置
您可以使用 设置通用状态机配置的一部分。有了它,您可以设置 和 autostart 标志
对于状态机。它还允许您注册实例、
配置转换冲突策略和区域执行策略。
以下示例演示如何使用:ConfigurationConfigurer
BeanFactory
StateMachineListener
ConfigurationConfigurer
@Configuration
@EnableStateMachine
public class Config17
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.machineId("myMachineId")
.beanFactory(new StaticListableBeanFactory())
.listener(new StateMachineListenerAdapter<States, Events>())
.transitionConflictPolicy(TransitionConflictPolicy.CHILD)
.regionExecutionPolicy(RegionExecutionPolicy.PARALLEL);
}
}
默认情况下,状态机标志处于禁用状态,因为所有
处理子状态的实例由状态机本身控制
并且无法自动启动。此外,离开要安全得多
是否应启动计算机
自动或不向用户。此标志仅控制
顶级状态机。autoStartup
在配置类中设置只是为了方便
你想或需要在那里做。machineId
注册实例部分也是为了
方便,但如果您想在
状态机生命周期,例如获取状态机的
start 和 stop 事件。请注意,您不能监听 state
计算机的启动事件(如果已启用),除非您注册侦听器
在配置阶段。StateMachineListener
autoStartup
您可以在多个
可以选择过渡路径。一个常见的用例是
machine 包含从子状态引出的匿名转换
和一个父状态,并且您希望定义一个策略,其中
选择。这是计算机实例中的全局设置,
默认为 。transitionConflictPolicy
CHILD
可用于配置 .它
允许您设置 ,它(如果存在)自动
包装任何使用 和 创建的
启用分布式模式。以下示例演示如何使用它:withDistributed()
DistributedStateMachine
StateMachineEnsemble
StateMachine
DistributedStateMachine
@Configuration
@EnableStateMachine
public class Config18
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withDistributed()
.ensemble(stateMachineEnsemble());
}
@Bean
public StateMachineEnsemble<States, Events> stateMachineEnsemble()
throws Exception {
// naturally not null but should return ensemble instance
return null;
}
}
有关分布式状态的更多信息,请参阅使用分布式状态。
该接口在内部用于
对状态机的结构进行一些健全性检查。其目的是
fail fast,而不是让常见的配置错误进入
状态机。默认情况下,会自动启用验证程序并使用 implementation 。StateMachineModelVerifier
DefaultStateMachineModelVerifier
使用 ,如果满足以下条件,您可以禁用验证程序或设置自定义验证程序
需要。以下示例显示了如何执行此操作:withVerifier()
@Configuration
@EnableStateMachine
public class Config19
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withVerifier()
.enabled(true)
.verifier(verifier());
}
@Bean
public StateMachineModelVerifier<States, Events> verifier() {
return new StateMachineModelVerifier<States, Events>() {
@Override
public void verify(StateMachineModel<States, Events> model) {
// throw exception indicating malformed model
}
};
}
}
有关配置模型的更多信息,请参见 StateMachine 配置模型。
的 和 配置方法
分别记录在 State Machine Security、Monitoring a State Machine 和 Using StateMachineRuntimePersister 中。withSecurity withMonitoring withPersistence |
12.10. 配置模型
StateMachineModelFactory
是一个钩子,允许您配置 StateMachine 模型
无需使用手动配置。本质上,它是一个第三方
integration 集成到配置模型中。
您可以通过以下方式挂接到配置模型
使用 .以下示例显示了如何执行此操作:StateMachineModelFactory
StateMachineModelConfigurer
@Configuration
@EnableStateMachine
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
@Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new CustomStateMachineModelFactory();
}
}
以下示例用于
定义两个状态 ( 和 ) 以及它们之间的事件 ()
国家:CustomStateMachineModelFactory
S1
S2
E1
public static class CustomStateMachineModelFactory implements StateMachineModelFactory<String, String> {
@Override
public StateMachineModel<String, String> build() {
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
Collection<StateData<String, String>> stateData = new ArrayList<>();
stateData.add(new StateData<String, String>("S1", true));
stateData.add(new StateData<String, String>("S2"));
StatesData<String, String> statesData = new StatesData<>(stateData);
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<String, String>(configurationData,
statesData, transitionsData);
return stateMachineModel;
}
@Override
public StateMachineModel<String, String> build(String machineId) {
return build();
}
}
定义自定义模型通常不是人们正在寻找的, 虽然这是可能的。然而,这是一个允许 对此配置模型的外部访问。 |
您可以在 Eclipse Modeling Support 中找到使用此 Model Factory 集成的示例。您可以找到有关自定义模型集成的更多通用信息 在 Developer Documentation(开发人员文档)中。
12.11. 需要记住的事情
当从
配置,记住 Spring Framework 的工作原理是值得的
和豆子。在下一个示例中,我们定义了一个普通配置,其中
状态 和 4 个过渡。所有过渡
由 或 保护。您必须确保将其创建为真正的 bean,因为它带有 , while is 不是。S1
S2
guard1
guard2
guard1
@Bean
guard2
这意味着该事件将获得条件 为 ,并将获得条件为 ,因为这些条件是
来自对这些函数的普通方法调用。E3
guard2
TRUE
E4
guard2
FALSE
但是,由于 被定义为 ,它由
Spring 框架。因此,对其方法的额外调用会导致
只有该实例的一个实例。Event 将首先获取
具有条件的代理实例,而 event 将得到相同的
实例 和 条件 (当方法调用是使用 定义时)。这不是 Spring State Machine 特有的行为。相反,它是
Spring Framework 如何与 bean 一起工作。
以下示例显示了这种安排的工作原理:guard1
@Bean
E1
TRUE
E2
TRUE
FALSE
@Configuration
@EnableStateMachine
public class Config1
extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("S1")
.state("S2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("S1").target("S2").event("E1").guard(guard1(true))
.and()
.withExternal()
.source("S1").target("S2").event("E2").guard(guard1(false))
.and()
.withExternal()
.source("S1").target("S2").event("E3").guard(guard2(true))
.and()
.withExternal()
.source("S1").target("S2").event("E4").guard(guard2(false));
}
@Bean
public Guard<String, String> guard1(final boolean value) {
return new Guard<String, String>() {
@Override
public boolean evaluate(StateContext<String, String> context) {
return value;
}
};
}
public Guard<String, String> guard2(final boolean value) {
return new Guard<String, String>() {
@Override
public boolean evaluate(StateContext<String, String> context) {
return value;
}
};
}
}
13. 状态机 ID
各种类和接口用作变量或
parameter 的 Method。本节将仔细研究与正常机器操作和实例化的关系。machineId
machineId
在运行时,确实没有任何大的操作
role 除外,以区分机器 — 例如,当
跟踪日志或进行更深入的调试。有很多不同的
如果有 Machine Instances 的话,开发人员很快就会迷失在翻译中
没有简单的方法来识别这些实例。因此,我们添加了将 .machineId
machineId
13.1. 使用@EnableStateMachine
在 Java 配置中设置 as then 会公开该值
用于日志。该方法也同样可用。以下示例使用该方法:machineId
mymachine
machineId
StateMachine.getId()
machineId
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.machineId("mymachine");
}
以下日志输出示例显示了 ID:mymachine
11:23:54,509 INFO main support.LifecycleObjectSupport [main] -
started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine
手动构建器(参见 通过 Builder 进行状态机)使用相同的配置 接口,这意味着行为是等效的。 |
13.2. 使用@EnableStateMachineFactory
如果您使用 并使用该 ID 请求新计算机,则可以看到相同的配置。
如下例所示:machineId
StateMachineFactory
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 或存储库。如果你想疯狂,你也可以使用 自定义 ,这是尽可能最低的
定义配置的级别。StateMachineModel
StateMachineFactory
StateMachineModel
所有这些都与 a 有什么关系? 还有一个具有以下签名的方法: 实现可以选择使用该方法。machineId
StateMachineModelFactory
StateMachineModel<S, E> build(String machineId)
StateMachineModelFactory
RepositoryStateMachineModelFactory
(请参阅存储库支持)用于支持持久
store 通过 Spring Data Repository 接口。例如,both 和 都有一个方法 (),用于构建不同的状态和
过渡 .其中 , 如果用作空
或 NULL,则默认为 repository configuration(在后备持久模型中)
没有已知的计算机 ID。machineId
StateRepository
TransitionRepository
List<T>
findByMachineId(String machineId)
machineId
RepositoryStateMachineModelFactory
machineId
目前,不区分
不同的计算机 ID,因为 UML 源总是来自同一
文件。在未来的版本中,这可能会发生变化。UmlStateMachineModelFactory |
14. 国家机工厂
有些用例需要动态创建状态机 而不是通过在编译时定义静态配置。例如 如果存在使用自己的状态机的自定义组件 而这些组件是动态创建的,不可能有 在应用程序启动期间构建的静态状态机。内部 状态机始终通过工厂接口构建。这 为您提供以编程方式使用此功能的选项。 状态机工厂的配置与所示完全相同 在本文档中的各种示例中,其中状态机配置 是硬编码的。
14.1. 通过适配器出厂
实际上,使用 works 创建状态机是通过工厂进行的,因此只需公开
该工厂通过其接口。以下示例使用 :@EnableStateMachine
@EnableStateMachineFactory
@EnableStateMachineFactory
@Configuration
@EnableStateMachineFactory
public class Config6
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
}
现在您已经习惯了创建工厂
你可以注入它并(按原样)使用它来
请求新的状态机。以下示例显示了如何执行此操作:@EnableStateMachineFactory
public class Bean3 {
@Autowired
StateMachineFactory<States, Events> factory;
void method() {
StateMachine<States,Events> stateMachine = factory.getStateMachine();
stateMachine.startReactively().subscribe();
}
}
14.2. 通过 Builder 进行状态机
使用适配器(如上所示)有一个限制,其
完成 Spring 类的要求和
应用程序上下文。虽然这是一个非常清晰的模型,用于配置
state machine 时,它会在编译时限制配置,
这并不总是用户想要做的。如果有要求
要构建更多动态状态机,您可以使用简单的构建器模式
以构造类似的实例。通过使用字符串作为状态和
事件,您可以使用此构建器模式来构建完全动态的 state
machines 在 Spring 应用程序上下文之外。以下示例
演示如何执行此操作:@Configuration
StateMachine<String, String> buildMachine1() throws Exception {
Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureStates()
.withStates()
.initial("S1")
.end("SF")
.states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4")));
return builder.build();
}
构建器在后台使用相同的配置接口,这些接口
该模型用于 Adapter 类。相同的模型用于
通过构建器的
方法。这意味着你可以与法线一起使用,或者你可以通过 builder 动态使用。@Configuration
EnumStateMachineConfigurerAdapter
StateMachineConfigurerAdapter
目前,、 、 、
和接口方法不能是
链接在一起,这意味着需要单独调用 Builder 方法。builder.configureStates() builder.configureTransitions() builder.configureConfiguration() |
以下示例使用 builder 设置许多选项:
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()
autoStart
BeanFactory
StateMachineListener
StateMachine
@Bean
BeanFactory
15. 使用延迟事件
发送事件时,它可能会触发 ,这可能会导致
如果状态机处于触发器为
评估成功。通常,这可能会导致
事件未被接受并被丢弃。但是,您可能希望
将此事件推迟到状态机进入另一个状态。在这种情况下,
您可以接受该事件。换句话说,一个事件
来得不是时候。EventTrigger
Spring Statemachine 提供了一种将事件推迟到以后的机制 加工。每个状态都可以有一个延迟事件列表。如果事件 在当前状态的 Deferred Event List occurs 中,保存该事件 (deferred) 以供将来处理,直到输入未列出的状态 其 Deferred Event 列表中的事件。当进入此类状态时, 状态机会自动调用任何已保存的不再 deferred,然后使用或丢弃这些事件。这是可能的 使 Superstate 在延迟的事件上定义转换 按子状态。遵循相同的分层状态机概念,子状态 优先于超状态,则事件被延迟,并且 transi 的 TRANSITION 未运行。对于正交区域, 当一个正交区域推迟事件而另一个正交区域接受事件时, accept 优先,事件被使用而不是延迟。
事件延迟最明显的用例是事件导致 转换到特定状态,然后返回状态机 恢复到其原始状态,其中第二个事件应导致相同的 过渡。以下示例显示了这种情况:
@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 中,其他事件可能会丢失,因为机器不再处于
可以处理事件。因此,延迟其中一些事件可以让机器
保留它们。以下示例说明如何配置此类安排:READY
DEPLOY
READY
READY
@Configuration
@EnableStateMachine
static class Config6 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("READY")
.state("DEPLOY", "DEPLOY")
.state("DONE")
.and()
.withStates()
.parent("DEPLOY")
.initial("DEPLOYPREPARE")
.state("DEPLOYPREPARE", "DONE")
.state("DEPLOYEXECUTE");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("READY").target("DEPLOY")
.event("DEPLOY")
.and()
.withExternal()
.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
.and()
.withExternal()
.source("DEPLOYEXECUTE").target("READY")
.and()
.withExternal()
.source("READY").target("DONE")
.event("DONE")
.and()
.withExternal()
.source("DEPLOY").target("DONE")
.event("DONE");
}
}
在前面的示例中,状态机使用嵌套状态而不是 flat
state 模型,因此可以直接在 substate 中延迟事件。
它还演示了在
子状态,然后覆盖
AND 表示状态机是否恰好处于 dispatch 事件时的状态。在事件未延迟的状态下,此事件将
在超级状态下处理。DEPLOY
DONE
DEPLOY
DONE
DEPLOYPREPARE
DONE
DEPLOYEXECUTE
DONE
16. 使用范围
对状态机中范围的支持非常有限,但您可以
通过以下两种方式之一使用普通的 Spring 注释来启用范围:session
@Scope
-
如果状态机是使用构建器手动构建的,并返回到 context 设置为 .
@Bean
-
通过配置适配器。
两者
这些需要存在,并且设置为 和 设置为。以下示例
显示两个用例:@Scope
scopeName
session
proxyMode
ScopedProxyMode.TARGET_CLASS
@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");
}
}
提示:有关如何使用会话范围的信息,请参阅 Scope。
将状态机的范围限定为 后,将其自动装配到
a 为每个会话提供一个新的状态机实例。
然后,每个状态机在失效时被销毁。
以下示例展示了如何在控制器中使用状态机:session
@Controller
HttpSession
@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. 使用操作
操作是可用于的最有用的组件之一 与状态机交互和协作。您可以运行操作 在状态机及其状态生命周期的不同位置——例如, 进入或退出状态或在过渡期间。 以下示例显示了如何在状态机中使用操作:
@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。以下示例定义了这些操作 (和 ):action1
action2
entry
exit
action3
@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);
}
}
您可以直接实现为匿名函数或创建
您自己的实现,并将相应的实现定义为
豆。Action
在前面的示例中,使用 SpEL 表达式将事件发送到
状态机。action3
Events.E1
StateContext 在使用 StateContext 中进行了介绍。 |
17.2. 响应式 Action
Normal interface 是一种简单的函数式方法,取回 void。在你阻止之前,这里没有任何阻碍
在方法本身中,这是一个有点问题,因为框架不能
了解它内部到底发生了什么。Action
StateContext
public interface Action<S, E> {
void execute(StateContext<S, E> context);
}
为了解决这个问题,我们在内部将处理方式更改为
处理普通 Java 的 take 和 return .这样我们就可以调用 action 并完全以响应式方式
execute 操作,仅当它被订阅且以非阻塞方式执行
等待完成。Action
Function
StateContext
Mono
public interface ReactiveAction<S, E> extends Function<StateContext<S, E>, Mono<Void>> {
}
内部旧接口由 Reactor Mono Runnable 包装,因为它
共享相同的 return 类型。我们无法控制您在该方法中做什么! |
18. 使用守卫
如 Things to Remember 中所示,和 beans 附加到条目和
退出状态。
以下示例还对事件使用 guards:guard1
guard2
@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:Guard
guardExpression
myvar
TRUE
@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 作为参数。Boolean
Guard
guardExpression()
18.2. 响应式守卫
Normal 接口是一个简单的函数式方法,获取并返回布尔值。在你阻止之前,这里没有任何阻碍
在方法本身中,这是一个有点问题,因为框架不能
了解它内部到底发生了什么。Guard
StateContext
public interface Guard<S, E> {
boolean evaluate(StateContext<S, E> context);
}
为了解决这个问题,我们在内部将处理方式更改为
处理普通 Java 的 take 和 return .这样我们就可以调用 guard 并且完全以响应方式
仅在订阅时以非阻塞方式对其进行评估
等待 return 值完成。Guard
Function
StateContext
Mono<Boolean>
public interface ReactiveGuard<S, E> extends Function<StateContext<S, E>, Mono<Boolean>> {
}
内部 old 接口由 Reactor Mono Function 包装。我们没有
控制您在该方法中执行的操作! |
19. 使用扩展状态
假设您需要创建一个状态机来跟踪 很多时候,用户按下键盘上的某个键,然后终止 当按键被按下 1000 次时。一个可能但非常幼稚的解决方案 将是每按 1000 次按键创建一个新状态。 您可能会突然出现一个天文数字 状态,这自然不是很实用。
这就是扩展状态变量通过不需要 以添加更多状态来驱动状态机更改。相反 您可以在过渡期间执行简单的变量更改。
StateMachine
有一个名为 .它返回一个
名为 的接口,该接口提供对扩展状态的访问
变量。您可以通过状态机直接访问这些变量,也可以在操作或转换的回调期间访问这些变量。
以下示例显示了如何执行此操作:getExtendedState()
ExtendedState
StateContext
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,您有两个选项:使用 或
监听回调。以下示例
使用的方法如下:StateMachineListener
extendedStateChanged(key, value)
extendedStateChanged
public class ExtendedStateVariableListener
extends StateMachineListenerAdapter<String, String> {
@Override
public void extendedStateChanged(Object key, Object value) {
// do something with changed variable
}
}
或者,您可以为 实现 Spring Application 上下文侦听器。如 侦听状态机事件 中所述,
您还可以监听所有事件。
以下示例用于侦听状态更改:OnExtendedStateChanged
StateMachineEvent
onApplicationEvent
public class ExtendedStateVariableEventListener
implements ApplicationListener<OnExtendedStateChanged> {
@Override
public void onApplicationEvent(OnExtendedStateChanged event) {
// do something with changed variable
}
}
20. 使用StateContext
StateContext
是最重要的对象之一
当使用状态机时,因为它被传递到各种方法中
和回调来给出状态机的当前状态,以及
它可能去哪里。您可以将其视为
当前状态机阶段的快照
是 Retreived 的时间。StateContext
在 Spring Statemachine 1.0.x 中,使用相对幼稚
就它如何被用来作为简单的 “POJO” 传递东西而言。
从 Spring Statemachine 1.1.x 开始,它的作用已经大大
通过使其成为状态机中的一等公民而得到改进。StateContext |
您可以使用 来访问以下内容:StateContext
-
当前 或 (或他们的 ,如果已知)。
Message
Event
MessageHeaders
-
状态机的 .
Extended State
-
本身。
StateMachine
-
到可能的状态机错误。
-
到当前 ,如果适用。
Transition
-
状态机的源状态。
-
状态机的目标状态。
-
当前 ,如 阶段.
Stage
StateContext
传递到各种组件中,例如 和 。Action
Guard
21. 触发过渡
驱动状态机是通过使用触发的转换来完成的
by 触发器。当前支持的触发器包括 和 。EventTrigger
TimerTrigger
21.1. 使用EventTrigger
EventTrigger
是最有用的触发器,因为它允许您
通过向状态机发送事件来直接与状态机交互。这些
事件也称为信号。您可以向过渡添加触发器
通过在配置期间将状态与其关联。
以下示例显示了如何执行此操作:
@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 列表。如果有
只有一个地区,此列表包含一个结果。sendEventCollect
Flux
Message<String> message1 = MessageBuilder
.withPayload("E1")
.build();
Mono<List<StateMachineEventResult<String, String>>> results =
stateMachine.sendEventCollect(Mono.just(message1));
results.subscribe();
在订阅返回的 flux 之前,什么都不会发生。从 StateMachineEventResult 中了解更多信息。 |
前面的示例通过构造 wrapping 来发送事件
a 并订阅返回的结果。 让
我们向事件添加任意的额外信息,然后该信息可见
到实施操作的时间(例如)。Mono
Message
Flux
Message
StateContext
消息标头通常会一直传递,直到机器运行
完成特定事件。例如,如果事件导致
transition 转换为 state 中,该 state 具有匿名 transition 到
state 中,原始事件可用于 state 中的操作或守卫。A B B |
也可以发送消息,而不仅仅是发送
一个带有 .Flux
Mono
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.2. 使用TimerTrigger
TimerTrigger
在需要触发某些操作时很有用
自动的。 已添加到
transition 的 TRANSITION。Trigger
目前,有两种类型的受支持的计时器,一种是触发 持续触发,并在进入源状态后触发。 以下示例演示如何使用触发器:
@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 时,我们定义
源状态和 的内部转换。S1
S2
S3
S1
S2
S1
S3
E1
E2
TimerTrigger
S2
S3
对于这两个转换,我们调用 bean () ),其中
源状态使用 和 使用 。
给出的值以毫秒为单位(在两种情况下都是毫秒或 1 秒)。Action
timerAction
S2
timer
S3
timerOnce
1000
一旦状态机收到 event ,它就会执行转换
from to 和计时器开始。当状态为 时,会运行并导致与该
state — 在本例中,是具有 defined.E1
S1
S2
S2
TimerTrigger
timerAction
一旦状态机收到 , 事件,它就会执行转换
from to 和计时器开始。此计时器仅执行一次
在 state 被输入之后 (在 timer 中定义的 delay 之后)。E2
S1
S3
在幕后,计时器是简单的触发器,可能会导致
transition 到 Happen 的 Transition。使用 keep 定义过渡
仅当 source state 为 active 时,触发才会触发并导致 transition。
Transition with 略有不同,因为它
仅在实际进入源状态时延迟后触发。timer() timerOnce() |
如果您希望在延迟后发生某些事情,请使用
在 State 进入时恰好一次。timerOnce() |
22. 监听状态机事件
在一些用例中,您想知道发生了什么 状态机、对某事做出反应或获取 调试目的。Spring Statemachine 提供了用于添加侦听器的接口。这些侦听器 然后给出一个选项,以便在各种 state 发生变化时获取回调, 操作,等等。
您基本上有两个选择:监听 Spring 应用程序 context 事件或直接将侦听器附加到状态机。两者 这些基本上提供相同的信息。一个产生 events 作为事件类,另一个通过侦听器生成回调 接口。这两者都有优点和缺点,我们将在后面讨论。
22.1. 应用程序上下文事件
应用程序上下文事件类包括 、 、 、这些可以按原样与 Spring 一起使用。OnTransitionStartEvent
OnTransitionEvent
OnTransitionEndEvent
OnStateExitEvent
OnStateEntryEvent
OnStateChangedEvent
OnStateMachineStart
OnStateMachineStop
StateMachineEvent
ApplicationListener
StateMachine
通过 发送上下文事件。
如果类使用 .
下面的示例从类中定义的 bean 获取一个:StateMachineEventPublisher
@Configuration
@EnableStateMachine
StateMachineApplicationEventListener
@Configuration
public class StateMachineApplicationEventListener
implements ApplicationListener<StateMachineEvent> {
@Override
public void onApplicationEvent(StateMachineEvent event) {
}
}
@Configuration
public class ListenerConfig {
@Bean
public StateMachineApplicationEventListener contextListener() {
return new StateMachineApplicationEventListener();
}
}
上下文事件也会通过使用 自动启用。
用于构建机器并注册为 Bean,
如下例所示:@EnableStateMachine
StateMachine
@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
通过使用 ,您可以扩展它和
实现所有回调方法或使用包含存根方法实现的类并选择哪些实现
以覆盖。
以下示例使用后一种方法:StateMachineListener
StateMachineListenerAdapter
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 类
() 扩展 .StateMachineEventListener
StateMachineListenerAdapter
listener 方法允许访问不同阶段上的各种更改。您可以在使用 StateContext
中找到有关它的更多信息。stateContext
StateContext
定义自己的侦听器后,您可以在
状态机。这是一个
flavor 是将其挂接在 Spring 配置中还是执行
在应用程序生命周期内的任何时间手动操作。
以下示例显示如何附加侦听器:addStateListener
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 应用程序上下文事件:StateMachineListener
contextEvents
@EnableStateMachine
@EnableStateMachineFactory
@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 风格的 轻松插入状态机功能的上下文集成 放入你的豆子里。
可用的注释已协调,以便能够访问相同的 可从 Listening to State Machine Events 获得的状态机执行点。
您可以使用注释来关联状态
machine 中。然后你就可以开始添加
该 bean 的方法的 supported 注释。
以下示例显示了如何执行此操作:@WithStateMachine
@WithStateMachine
public class Bean1 {
@OnTransition
public void anyTransition() {
}
}
您还可以从
Application context 结合使用。
以下示例显示了如何执行此操作:name
@WithStateMachine(name = "myMachineBeanName")
public class Bean2 {
@OnTransition
public void anyTransition() {
}
}
有时,使用起来更方便,这是一些事情
您可以设置以更好地识别多个实例。此 ID 映射到
接口中的方法。
以下示例演示如何使用它:machine id
getId()
StateMachine
@WithStateMachine(id = "myMachineId")
public class Bean16 {
@OnTransition
public void anyTransition() {
}
}
当使用 StateMachineFactory 生成状态机时,使用 dynamic provided 的状态机,bean name 将默认为无法使用,因为仅在运行时才知道。id
stateMachine
@WithStateMachine (id = "some-id")
id
在这种情况下,使用 or 和工厂生成的所有状态机都将附加到您的一个或多个 bean。@WithStateMachine
@WithStateMachine(name = "stateMachine")
您也可以用作元注释,如图所示
在前面的示例中。在这种情况下,您可以使用 .
以下示例显示了如何执行此操作:@WithStateMachine
WithMyBean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@WithStateMachine(name = "myMachineBeanName")
public @interface WithMyBean {
}
这些方法的返回类型无关紧要,并且实际上是 丢弃。 |
23.1. 启用集成
您可以使用
注解,它导入所需的
配置导入到 Spring Application Context 中。两者都已经是
使用此注释进行注释,因此无需再次添加它。
但是,如果机器在构建和配置时没有
配置适配器,则必须使用 才能将这些功能与 一起使用。
以下示例显示了如何执行此操作:@WithStateMachine
@EnableWithStateMachine
@EnableStateMachine
@EnableStateMachineFactory
@EnableWithStateMachine
@WithStateMachine
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 的工作原理,请参阅使用 StateContext
。
有关方法参数之间的差异,请参阅定义 单独的注释。 |
实际上,所有带 Comments 的方法都是通过使用 Spring SPel 来调用的
表达式,这些表达式在此过程中动态构建。要使
this work,这些表达式需要有一个 root 对象(它们根据该对象进行求值)。
此根对象是一个 .我们还制作了一些
在内部进行调整,以便可以访问方法
直接访问,而无需通过上下文句柄。StateContext
StateContext
最简单的方法参数是 a 本身。
以下示例演示如何使用它:StateContext
@WithStateMachine
public class Bean3 {
@OnTransition
public void anyTransition(StateContext<String, String> stateContext) {
}
}
您可以访问其余内容。
参数的数量和顺序无关紧要。
以下示例显示了如何访问内容的各个部分:StateContext
StateContext
@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
@OnTransitionEnd
这些注释的行为完全相同。为了展示它们的工作原理,我们展示了
如何使用。在此注解中,属性的
您可以使用 和 来限定过渡。如果 和 留空,则匹配任何过渡。
以下示例演示如何使用注释
(记住这一点,并以相同的方式工作):@OnTransition
source
target
source
target
@OnTransition
@OnTransitionStart
@OnTransitionEnd
@WithStateMachine
public class Bean5 {
@OnTransition(source = "S1", target = "S2")
public void fromS1ToS2() {
}
@OnTransition
public void anyTransition() {
}
}
默认情况下,不能将注解与 state 和
由于 Java 语言限制,您创建的 event 枚举。
因此,您需要使用字符串表示形式。@OnTransition
此外,您还可以通过将所需的参数添加到方法来访问 AND。方法
然后,使用这些参数自动调用。
以下示例显示了如何执行此操作:Event Headers
ExtendedState
@WithStateMachine
public class Bean6 {
@StatesOnTransition(source = States.S1, target = States.S2)
public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
}
}
但是,如果您想拥有类型安全的注解,则可以
创建新注释并用作元注释。
此用户级注释可以引用实际状态和
events 枚举,框架会尝试以相同的方式匹配这些枚举。
以下示例显示了如何执行此操作:@OnTransition
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {
States[] source() default {};
States[] target() default {};
}
在前面的示例中,我们创建了一个注释,该注释以类型安全的方式定义 AND。
下面的示例在 bean 中使用该 Comments:@StatesOnTransition
source
target
@WithStateMachine
public class Bean7 {
@StatesOnTransition(source = States.S1, target = States.S2)
public void fromS1ToS2() {
}
}
23.4. 状态注解
状态的以下注释可用:、 和 。以下示例演示如何使用注解(
其他两个的工作方式相同):@OnStateChanged
@OnStateEntry
@OnStateExit
OnStateChanged
@WithStateMachine
public class Bean8 {
@OnStateChanged
public void anyStateChange() {
}
}
就像使用 Transition Annotations 一样,您可以定义 target 和 source 状态。以下示例显示了如何执行此操作:
@WithStateMachine
public class Bean9 {
@OnStateChanged(source = "S1", target = "S2")
public void stateChangeFromS1toS2() {
}
}
为了类型安全,需要用作元注释为枚举创建新的注释。以下示例说明如何执行此操作:@OnStateChanged
@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 的方法的行为方式相同,如下例所示:
@WithStateMachine
public class Bean11 {
@OnStateEntry
public void anyStateEntry() {
}
@OnStateExit
public void anyStateExit() {
}
}
23.5. 事件注解
有一个与事件相关的注释。它被命名为 。
如果指定了该属性,则可以侦听特定事件
接受。如果未指定事件,则可以列出任何非
接受。以下示例显示了使用 Comments 的两种方法:@OnEventNotAccepted
event
@OnEventNotAccepted
@WithStateMachine
public class Bean12 {
@OnEventNotAccepted
public void anyEventNotAccepted() {
}
@OnEventNotAccepted(event = "E1")
public void e1EventNotAccepted() {
}
}
23.6. 状态机注解
以下注释可用于状态机:、 和 。@OnStateMachineStart
@OnStateMachineStop
@OnStateMachineError
在状态机启动和停止期间,将调用生命周期方法。
下面的示例演示如何使用和侦听这些事件:@OnStateMachineStart
@OnStateMachineStop
@WithStateMachine
public class Bean13 {
@OnStateMachineStart
public void onStateMachineStart() {
}
@OnStateMachineStop
public void onStateMachineStop() {
}
}
如果状态机出现异常错误,则调用 annotation 。以下示例演示如何使用它:@OnStateMachineStop
@WithStateMachine
public class Bean14 {
@OnStateMachineError
public void onStateMachineError() {
}
}
24. 使用StateMachineAccessor
StateMachine
是与状态机通信的主接口。
有时,您可能需要变得更加动态和
以编程方式访问状态机的内部结构及其
嵌套计算机和区域。对于这些用例,公开了一个名为 的功能接口,该接口提供了
用于访问 individual 和 instances 的接口。StateMachine
StateMachineAccessor
StateMachine
Region
StateMachineFunction
是一个简单的功能接口,它允许
您可以将接口应用于状态机。跟
JDK 7 创建的代码有点冗长。但是,使用 JDK 8 lambda 时,
DOCE 相对不冗长。StateMachineAccess
该方法允许访问
状态机。以下示例演示如何使用它:doWithAllRegions
Region
stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.setRelay(stateMachine));
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access -> access.setRelay(stateMachine));
该方法允许访问
状态机。以下示例演示如何使用它:doWithRegion
Region
stateMachine.getStateMachineAccessor().doWithRegion(function -> function.setRelay(stateMachine));
stateMachine.getStateMachineAccessor()
.doWithRegion(access -> access.setRelay(stateMachine));
该方法允许访问
状态机。以下示例演示如何使用它:withAllRegions
Region
for (StateMachineAccess<String, String> access : stateMachine.getStateMachineAccessor().withAllRegions()) {
access.setRelay(stateMachine);
}
stateMachine.getStateMachineAccessor().withAllRegions()
.stream().forEach(access -> access.setRelay(stateMachine));
该方法允许访问
状态机。以下示例演示如何使用它:withRegion
Region
stateMachine.getStateMachineAccessor()
.withRegion().setRelay(stateMachine);
25. 使用StateMachineInterceptor
除了使用接口,您还可以
使用 .一个概念上的区别是,您可以使用
interceptor 拦截和停止当前状态
更改或更改其过渡逻辑。而不是实现完整的接口,
您可以使用名为 to override 的 Adapter 类
默认的 no-op 方法。StateMachineListener
StateMachineInterceptor
StateMachineInterceptorAdapter
您可以通过 注册拦截器。的概念
拦截器是一个相对较深的内部特征,因此不是
直接通过接口公开。StateMachineAccessor
StateMachine
以下示例显示了如何添加 并覆盖 selected
方法:StateMachineInterceptor
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 Security 相当熟悉,这意味着 我们不会详细介绍整体安全框架的工作原理。为 此信息,您应该阅读 Spring Security 参考文档 (可在此处获得)。 |
安全的第一级防御自然是保护事件, 这真的驱动着什么 发生在状态机中。然后,您可以定义更精细的安全设置 用于过渡和操作。这与允许员工进入架构物类似 然后授予对架构物内特定房间的访问权限,甚至 以打开和关闭特定房间中的灯。如果您信任 您的用户,事件安全性可能就是您所需要的。如果没有, 您需要应用更详细的安全性。
您可以在了解安全性中找到更多详细信息。
有关完整示例,请参阅 Security 示例。 |
26.1. 配置安全性
安全性的所有通用配置都在 中完成,该配置可从 获得。默认情况下,安全性处于禁用状态,
即使 Spring Security 类是
目前。以下示例说明如何启用安全性:SecurityConfigurer
StateMachineConfigurationConfigurer
@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 和
转换。如果您未定义决策管理器或
将它们设置为 ,默认管理器将在内部创建。AccessDecisionManager
null
26.2. 保护事件
事件安全性在全局级别由 定义。
以下示例显示如何启用事件安全性:SecurityConfigurer
@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 需要返回 或 。我们还定义了一个
的属性 和 的 。有关使用属性的更多信息
和表达式,请参阅使用安全属性和表达式。true
TRUE
TRUE
TRUE
FALSE
ROLE_ANONYMOUS
ComparisonType
ANY
26.3. 保护过渡
您可以全局定义过渡安全性,如下例所示。
@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);
}
}
如果在转换本身中定义了安全性,则它会覆盖任何 全局设置安全性。以下示例显示了如何执行此操作:
@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')");
}
}
有关使用属性和表达式的更多信息,请参阅使用安全属性和表达式。
26.4. 保护操作
状态中的操作没有专用的安全定义
machine 中,但您可以使用全局方法 security 来保护操作
来自 Spring Security。这需要一个 be
定义为代理,其方法用 .以下示例显示了如何执行此操作:Action
@Bean
execute
@Secured
@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 启用全局方法安全性。 以下示例显示了如何执行此操作:
@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 参考指南(在此处提供)。
26.5. 使用安全属性和表达式
通常,您可以通过以下两种方式之一定义安全属性:通过 使用安全属性和通过使用安全表达式。 属性更易于使用,但在 功能性。表达式提供了更多功能,但有点 更难使用。
26.5.1. 通用属性用法
默认情况下,events 和
转换都使用 ,这意味着您可以使用角色属性
来自 Spring Security。AccessDecisionManager
RoleVoter
对于属性,我们有三种不同的比较类型: 、 和 。这些比较类型映射到默认访问决策管理器
(分别为 、 和 )。
如果您已定义 custom ,则比较类型为
有效地丢弃,因为它仅用于创建默认管理器。ANY
ALL
MAJORITY
AffirmativeBased
UnanimousBased
ConsensusBased
AccessDecisionManager
26.5.2. 泛型表达式的用法
安全表达式必须返回 或 。TRUE
FALSE
表达式根对象的基类是 .它提供了一些常见的表达式,这些表达式
在 Transition 和 Event Security 中均可用。下表
描述最常用的内置表达式:SecurityExpressionRoot
表达 | 描述 |
---|---|
|
如果当前委托人具有指定的角色,则返回。由
default,如果提供的角色不以 开头,则为
添加。您可以通过修改 on . |
|
如果当前委托人具有任何提供的
roles (以逗号分隔的字符串列表形式给出)。默认情况下,如果每个
提供的角色不以 开头,而是添加。您可以自定义此
通过修改 on . |
|
如果当前委托人具有指定的授权,则返回。 |
|
如果当前委托人具有任何提供的
roles (以逗号分隔的字符串列表形式给出)。 |
|
允许直接访问表示 当前用户。 |
|
允许直接访问获取的当前对象
从 . |
|
始终计算结果为 。 |
|
始终计算结果为 。 |
|
如果当前主体是匿名用户,则返回。 |
|
如果当前委托人是 remember-me 用户,则返回。 |
|
如果用户不是匿名的,则返回。 |
|
如果用户不是匿名用户或记住我用户,则返回。 |
|
如果用户有权访问为
given 权限 — 例如 . |
|
如果用户有权访问为
given 权限 — 例如 . |
26.6. 了解安全性
本节提供了有关安全性在 状态机。你可能真的不需要知道,但确实需要 保持透明总是更好,而不是隐藏所有的魔法 发生在幕后。
只有当 Spring Statemachine 在封闭的
garden 中,用户无法直接访问应用程序,因此可能会
在本地线程中修改 Spring Security 的保留。
如果用户控制 JVM,那么实际上就没有安全性
完全。SecurityContext |
安全集成点是使用 StateMachineInterceptor
创建的,然后自动添加到
状态机(如果启用了安全性)。特定的类是 ,它拦截事件和
转换。然后,此拦截器会咨询 Spring Security 的,以确定是否可以发送事件,或者是否可以发送 transition
执行。实际上,如果决策或投票导致异常,则事件或转换将被拒绝。StateMachineSecurityInterceptor
AccessDecisionManager
AccessDecisionManager
由于 Spring Security 的工作原理,我们
每个 Secure Object 需要一个 IT 实例。这就是为什么有
是事件和过渡的不同管理器。在这种情况下,事件
和 transitions 是我们保护的不同类对象。AccessDecisionManager
默认情况下,对于事件,投票者(、 和 )将添加到 .EventExpressionVoter
EventVoter
RoleVoter
AccessDecisionManager
默认情况下,对于转换,投票者 (、 和 ) 将添加到 .TransitionExpressionVoter
TransitionVoter
RoleVoter
AccessDecisionManager
27. 状态机错误处理
如果状态机在状态转换期间检测到内部错误 logic,它可能会引发异常。处理此异常之前 在内部,您有机会进行拦截。
通常,您可以使用来拦截错误和
下面的清单显示了一个示例:StateMachineInterceptor
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 事件侦听器。有关这些内容的更多信息,请参阅侦听状态机事件。StateMachineListener
话虽如此,下面的示例显示了一个简单的侦听器:
public class ErrorStateMachineListener
extends StateMachineListenerAdapter<String, String> {
@Override
public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
// do something with error
}
}
以下示例显示了通用检查:ApplicationListener
StateMachineEvent
public class GenericApplicationEventListener
implements ApplicationListener<StateMachineEvent> {
@Override
public void onApplicationEvent(StateMachineEvent event) {
if (event instanceof OnStateMachineError) {
// do something with error
}
}
}
您也可以直接定义
仅识别实例,如下例所示:ApplicationListener
StateMachineEvent
public class ErrorApplicationEventListener
implements ApplicationListener<OnStateMachineError> {
@Override
public void onApplicationEvent(OnStateMachineError event) {
// do something with error
}
}
为 transition 定义的 Action 也有自己的错误处理 逻辑。请参阅 Transition Action 错误处理。 |
使用响应式 API 时,可能会出现 Action execution 错误
从 StateMachineEventResult 返回。拥有简单的机器
操作中的错误正在转换为 状态 。S1
@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 获取。
@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");
}
进入/退出操作中的错误不会阻止转换的发生。 |
29. 持久化状态机
传统上,状态机的实例在 running 程序。您可以通过使用 动态构建器和工厂,允许 State Machine 实例化。构建状态机的实例是一个 操作相对较重。因此,如果您需要(例如)处理 使用状态机在数据库中进行任意状态更改,您需要 找到一种更好、更快的方法来做到这一点。
persist 功能允许您保存状态机的状态 添加到外部存储库中,然后根据 serialized 状态。例如,如果您有一个数据库表保存 orders,则使用 state 更新 Order state 的成本太高了 machine 的实例。 persist 功能允许您在没有 实例化新的状态机实例。
虽然您可以使用 构建自定义持久性功能,但它有一个概念问题。当侦听器
通知状态更改,则状态更改已发生。如果
自定义持久化方法无法更新序列化的
state 中,状态机中的状态和
外部存储库将处于不一致状态。StateMachineListener
您可以改用状态机拦截器来尝试保存
序列化状态到外部存储中
state machine 内的 change 进行如果该拦截器回调失败,
您可以停止状态更改尝试,而不是以
inconsistent 状态,然后您可以手动处理此错误。有关如何使用拦截器的信息,请参见使用 StateMachineInterceptor
。
29.1. 使用StateMachineContext
您不能使用普通 java 持久化
序列化,因为对象图太丰富并且包含太多
对其他 Spring 上下文类的依赖。 是状态机的运行时表示形式,您可以使用它来
将现有计算机还原到由特定对象表示的状态。StateMachine
StateMachineContext
StateMachineContext
StateMachineContext
包含两种不同的信息包含
对于子上下文。这些通常在机器包含
正交区域。首先,上下文可以具有子上下文的列表
可以按原样使用,就好像它们存在一样。其次,您可以
包括原始上下文子级
未到位。这些子引用实际上是
保留运行多个并行区域的计算机
独立地。
Data Multi Persist 示例显示 如何持久保存并行区域。 |
29.2. 使用StateMachinePersister
构建并恢复状态机
如果完成的话,它总是有一点点 “黑魔法”
手动地。该界面旨在缓解这些
通过提供和方法进行操作。默认的
此接口的实现是 。StateMachineContext
StateMachinePersister
persist
restore
DefaultStateMachinePersister
我们可以通过以下方式展示如何使用 a
A 测试中的片段。我们首先创建两个类似的配置
( 和 ) 表示状态机。请注意,我们可以构建不同的
机器以其他方式进行演示,但 this way
适用于这种情况。以下示例配置两个状态机:StateMachinePersister
machine1
machine2
@Configuration
@EnableStateMachine(name = "machine1")
static class Config1 extends Config {
}
@Configuration
@EnableStateMachine(name = "machine2")
static class Config2 extends Config {
}
static class Config extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S1")
.state("S2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1");
}
}
由于我们正在使用对象,因此我们可以在内存中创建一个
实现。StateMachinePersist
此内存中示例仅用于演示目的。真实 applications 中,您应该使用真正的持久存储实现。 |
下面的清单显示了如何使用内存中示例:
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 中。然后我们可以持久保存它并恢复 .以下示例显示了如何执行此操作:machine1
S2
E1
machine2
InMemoryStateMachinePersist stateMachinePersist = new InMemoryStateMachinePersist();
StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);
StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
stateMachine1.startReactively().block();
stateMachine1
.sendEvent(Mono.just(MessageBuilder
.withPayload("E1").build()))
.blockLast();
assertThat(stateMachine1.getState().getIds()).containsExactly("S2");
persister.persist(stateMachine1, "myid");
persister.restore(stateMachine2, "myid");
assertThat(stateMachine2.getState().getIds()).containsExactly("S2");
29.3. 使用 Redis
RepositoryStateMachinePersist
(实现)支持将状态机持久化到 Redis 中。
具体实现是一个 ,它使用序列化来
将 A 持久化为 。StateMachinePersist
RedisStateMachineContextRepository
kryo
StateMachineContext
Redis
对于 ,我们有一个与 Redis 相关的实现,它采用
a 并用作其 Context 对象。StateMachinePersister
RedisStateMachinePersister
StateMachinePersist
String
有关详细用法,请参阅 事件服务 示例。 |
RedisStateMachineContextRepository
需要一个让它工作。我们建议使用 a for it,如前面的示例所示。RedisConnectionFactory
JedisConnectionFactory
29.4. 使用StateMachineRuntimePersister
StateMachineRuntimePersister
是一个简单的扩展,它添加了一个接口级方法来与之关联。然后,这个拦截器是
需要在 state 更改期间持久化机器,而无需
停止和启动计算机。StateMachinePersist
StateMachineInterceptor
目前,此接口有
支持的 Spring Data Repositories。这些实现是 、 、
和。JpaPersistingStateMachineInterceptor
MongoDbPersistingStateMachineInterceptor
RedisPersistingStateMachineInterceptor
有关详细用法,请参阅 Data Persist 示例。 |
30. Spring Boot 支持
自动配置模块 () 包含所有
与 Spring Boot 集成的逻辑,它为
自动配置和执行器。您所需要的只是拥有这个 Spring Statemachine
库作为启动应用程序的一部分。spring-statemachine-autoconfigure
30.1. 监控和跟踪
BootStateMachineMonitor
是自动创建的,并与
状态机。 是与 Spring Boot 和端点集成的自定义实现
通过自定义 .或者,您可以禁用此自动配置
通过将密钥设置为 .Monitoring 示例显示了如何使用此自动配置。BootStateMachineMonitor
StateMachineMonitor
MeterRegistry
StateMachineTraceRepository
spring.statemachine.monitor.enabled
false
30.2. 仓库配置
如果从 Classpath 中找到所需的类,则 Spring Data Repositories 实体类扫描会自动配置 以获取存储库支持。
当前支持的配置包括 、 和 。您可以分别使用 、 和 属性来禁用存储库自动配置。JPA
Redis
MongoDB
spring.statemachine.data.jpa.repositories.enabled
spring.statemachine.data.redis.repositories.enabled
spring.statemachine.data.mongo.repositories.enabled
31. 监控状态机
您可以使用 获取有关
执行过渡和操作所花费的时间的持续时间。以下清单
演示如何实现此接口。StateMachineMonitor
public class TestStateMachineMonitor extends AbstractStateMachineMonitor<String, String> {
@Override
public void transition(StateMachine<String, String> stateMachine, Transition<String, String> transition,
long duration) {
}
@Override
public void action(StateMachine<String, String> stateMachine,
Function<StateContext<String, String>, Mono<Void>> action, long duration) {
}
}
拥有实施后,您可以将其添加到
通过配置的状态机,如下例所示:StateMachineMonitor
@Configuration
@EnableStateMachine
public class Config1 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withMonitoring()
.monitor(stateMachineMonitor());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1");
}
@Bean
public StateMachineMonitor<String, String> stateMachineMonitor() {
return new TestStateMachineMonitor();
}
}
有关详细用法,请参阅 Monitoring 示例。 |
32. 使用分布式状态
分布式状态可能是 Spring 状态机。究竟什么是分布式状态?状态 在单个状态机中自然很容易理解, 但是,当需要引入共享分布式状态时 通过状态机,事情会变得有点复杂。
分布式状态功能仍是一项预览功能,并非 但被认为在此特定版本中是稳定的。我们期待这一点 功能使其成熟到第一个正式版本。 |
分布式状态机是通过包装实际实例的类实现的
的 . 拦截
通信并与
通过接口处理的分布式状态抽象。根据实际实现,
您还可以使用该接口序列化 ,其中包含足够的信息来重置 .DistributedStateMachine
StateMachine
DistributedStateMachine
StateMachine
StateMachineEnsemble
StateMachinePersist
StateMachineContext
StateMachine
虽然分布式状态机是通过抽象实现的, 当前仅存在一个实现。它基于 Zookeeper。
以下示例显示如何配置基于 Zookeeper 的分布式状态 machine' 的
@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 的分布式 附录中的状态机。
32.1. 使用ZookeeperStateMachineEnsemble
ZookeeperStateMachineEnsemble
本身需要两个强制设置,
的实例和 .客户端是 ,路径是实例中树的根。curatorClient
basePath
CuratorFramework
Zookeeper
(可选)您可以设置 ,如果集成中不存在成员,则默认为并清除现有数据。您可以设置
如果要在
应用程序重新启动。cleanState
TRUE
FALSE
(可选)您可以设置 (defaults
到 ) 保留状态更改的历史记录。这个
set 必须是 2 的幂。 通常是一个不错的默认值
价值。如果特定状态机被
大小,则会进入 Error 状态并与
ensemble 的 Ensemble 表示它已经失去了它的历史和完全重建
synchronized 状态。logSize
32
32
33. 测试支持
我们还添加了一组实用程序类来简化状态测试 machine 实例。这些在框架本身中使用,但也 对最终用户非常有用。
StateMachineTestPlanBuilder
构建一个 、
它有一个方法(称为 )。该方法运行一个计划。 包含一个 Fluent Builder API,允许您添加
步骤。在这些步骤中,您可以发送事件并检查
各种条件,例如状态更改、转换和扩展状态
变量。StateMachineTestPlan
test()
StateMachineTestPlanBuilder
以下示例用于构建状态机:StateMachineBuilder
private StateMachine<String, String> buildMachine() throws Exception {
StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.autoStartup(true);
builder.configureStates()
.withStates()
.initial("SI")
.state("S1");
builder.configureTransitions()
.withExternal()
.source("SI").target("S1")
.event("E1")
.action(c -> {
c.getExtendedState().getVariables().put("key1", "value1");
});
return builder.build();
}
在下面的测试计划中,我们有两个步骤。首先,我们检查初始
state () 确实已设置。其次,我们发送一个事件 () 并期望
发生一个状态更改,并预期机器最终处于 状态 。
下面的清单显示了测试计划:SI
E1
S1
StateMachine<String, String> machine = buildMachine();
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.defaultAwaitTime(2)
.stateMachine(machine)
.step()
.expectStates("SI")
.and()
.step()
.sendEvent("E1")
.expectStateChanged(1)
.expectStates("S1")
.expectVariable("key1")
.expectVariable("key1", "value1")
.expectVariableWith(hasKey("key1"))
.expectVariableWith(hasValue("value1"))
.expectVariableWith(hasEntry("key1", "value1"))
.expectVariableWith(not(hasKey("key2")))
.and()
.build();
plan.test();
这些实用程序还在框架中用于测试分布式 状态机功能。请注意,您可以将多台计算机添加到一个计划中。 如果您添加多台计算机,您还可以选择 将事件发送到特定计算机、随机计算机或所有计算机。
前面的测试示例使用以下 Hamcrest 导入:
预期结果的所有可能选项都记录在 StateMachineTestPlanStepBuilder 的 Javadoc 中。 |
34. Eclipse 建模支持
支持使用 UI 建模定义状态机配置 通过 Eclipse Papyrus 框架。
在 Eclipse 向导中,您可以使用 UML 图创建新的 Papyrus 模型
语言。在此示例中,它名为 。那么你
有一个选项可以从各种图表类型中进行选择,并且您必须选择一个 .simple-machine
StateMachine
Diagram
我们想要创建一台具有两种状态 ( 和 ) 的机器,其中 是初始状态。然后,我们需要创建 event 来执行 transition
从 到 。在 Papyrus 中,机器看起来就像什么东西
以下示例:S1
S2
S1
E1
S1
S2
在后台,原始 UML 文件将类似于以下示例:
<?xml version="1.0" encoding="UTF-8"?>
<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_AMP3IP8fEeW45bORGB4c_A" name="RootElement">
<packagedElement xmi:type="uml:StateMachine" xmi:id="_AMRFQP8fEeW45bORGB4c_A" name="StateMachine">
<region xmi:type="uml:Region" xmi:id="_AMRsUP8fEeW45bORGB4c_A" name="Region1">
<transition xmi:type="uml:Transition" xmi:id="_chgcgP8fEeW45bORGB4c_A" source="_EZrg4P8fEeW45bORGB4c_A" target="_FAvg4P8fEeW45bORGB4c_A">
<trigger xmi:type="uml:Trigger" xmi:id="_hs5jUP8fEeW45bORGB4c_A" event="_NeH84P8fEeW45bORGB4c_A"/>
</transition>
<transition xmi:type="uml:Transition" xmi:id="_egLIoP8fEeW45bORGB4c_A" source="_Fg0IEP8fEeW45bORGB4c_A" target="_EZrg4P8fEeW45bORGB4c_A"/>
<subvertex xmi:type="uml:State" xmi:id="_EZrg4P8fEeW45bORGB4c_A" name="S1"/>
<subvertex xmi:type="uml:State" xmi:id="_FAvg4P8fEeW45bORGB4c_A" name="S2"/>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_Fg0IEP8fEeW45bORGB4c_A"/>
</region>
</packagedElement>
<packagedElement xmi:type="uml:Signal" xmi:id="_L01D0P8fEeW45bORGB4c_A" name="E1"/>
<packagedElement xmi:type="uml:SignalEvent" xmi:id="_NeH84P8fEeW45bORGB4c_A" name="SignalEventE1" signal="_L01D0P8fEeW45bORGB4c_A"/>
</uml:Model>
打开已定义为 UML 的现有模型时,您有三个
文件:、 和 .如果模型不是在
eclipse 的会话中,它不了解如何打开一个实际的 state
图表。这是 Papyrus 插件中的一个已知问题,并且有一个简单的
解决方法。在 Papyrus 透视图中,您可以看到
您的模型。双击 Diagram StateMachine Diagram,它
指示 Eclipse 在其适当的 Papyrus 中打开此特定模型
modeling 插件。.di .notation .uml |
34.1. 使用UmlStateMachineModelFactory
在项目中放置 UML 文件后,您可以将其导入到
配置,其中 与模型关联。 是一家懂得
处理 Eclipse Papyrus_generated UML 结构。源 UML 文件可以
可以作为 Spring 或普通位置字符串给出。
以下示例说明如何创建 的实例:StateMachineModelConfigurer
StateMachineModelFactory
UmlStateMachineModelFactory
Resource
UmlStateMachineModelFactory
@Configuration
@EnableStateMachine
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
@Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new UmlStateMachineModelFactory("classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
}
}
像往常一样, Spring Statemachine 与 guard 和 操作,这些操作被定义为 bean。这些需要挂接到 UML 中 通过其内部建模结构。以下部分显示 如何在 UML 定义中定义自定义的 Bean 引用。 请注意,也可以手动注册特定方法 而不将它们定义为 bean。
如果创建为 Bean,则会自动连接其以查找已注册的操作,并且
警卫。您还可以手动定义 ,然后使用该 API 查找这些
组件。工厂还具有 registerAction 和 registerGuard 方法,您可以使用它们来注册这些组件。了解更多
有关此内容,请参阅使用 StateMachineComponentResolver
。UmlStateMachineModelFactory
ResourceLoader
StateMachineComponentResolver
UML 模型在实现(如 Spring Statemachine 本身。Spring Statemachine 留下了如何实现许多功能,并且 功能,直到实际实现。以下部分 通过 Spring Statemachine 如何基于 Eclipse Papyrus 插件。
34.1.1. 使用StateMachineComponentResolver
下一个示例展示了如何使用
a ,它分别注册 和 函数。请注意,这些组件
不会创建为 Bean。下面的清单显示了该示例:UmlStateMachineModelFactory
StateMachineComponentResolver
myAction
myGuard
@Configuration
@EnableStateMachine
public static class Config2 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
@Bean
public StateMachineModelFactory<String, String> modelFactory() {
UmlStateMachineModelFactory factory = new UmlStateMachineModelFactory(
"classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
factory.setStateMachineComponentResolver(stateMachineComponentResolver());
return factory;
}
@Bean
public StateMachineComponentResolver<String, String> stateMachineComponentResolver() {
DefaultStateMachineComponentResolver<String, String> resolver = new DefaultStateMachineComponentResolver<>();
resolver.registerAction("myAction", myAction());
resolver.registerGuard("myGuard", myGuard());
return resolver;
}
public Action<String, String> myAction() {
return new Action<String, String>() {
@Override
public void execute(StateContext<String, String> context) {
}
};
}
public Guard<String, String> myGuard() {
return new Guard<String, String>() {
@Override
public boolean evaluate(StateContext<String, String> context) {
return false;
}
};
}
}
34.2. 创建模型
我们首先创建一个空的状态机模型,如下图所示:
您可以先创建一个新模型并为其命名,如下图所示:
然后你需要选择 StateMachine Diagram,如下所示:
您最终会得到一个空的状态机。
在前面的图像中,您应该创建了一个名为 .
您应该得到三个文件:、 、 和 .然后,您可以在任何其他
Eclipse 实例。此外,您还可以导入到
Spring Statemachine 的 Statemachine 中。model
model.di
model.notation
model.uml
model.uml
34.3. 定义 State
状态标识符来自图中的组件名称。 您的计算机中必须有一个初始状态,您可以通过添加 一个根元素,然后绘制到您自己的初始状态的过渡, 如下图所示:
在上图中,我们添加了一个根元素和一个初始状态 ()。然后我们画了一个过渡
来表示这是一个初始状态。S1
S1
在上图中,我们添加了第二个状态 () 并在
S1 和 S2(表示我们有两种状态)。S2
34.4. 定义事件
要将事件与过渡关联,您需要创建一个 Signal
(在本例中)。为此,请选择 RootElement → New Child → Signal。
下图显示了结果:E1
然后,您需要使用新 Signal 创建 SignalEvent。
为此,请选择 RootElement → New Child → SignalEvent。
下图显示了结果:E1
现在,您已经定义了 ,您可以使用它来关联
带有 transition 的 Trigger。有关更多信息,请参阅定义过渡。SignalEvent
34.5. 定义过渡
您可以通过在
源状态和目标状态。在前面的图像中,我们有状态 和 以及
两者之间的匿名过渡。我们希望将 event 与该转换相关联。我们选择一个过渡,创建一个新的
trigger,并为此定义 SignalEventE1,如下图所示:S1
S2
E1
这为您提供了如下图所示的排列方式:
如果省略过渡的 SignalEvent,它将变为 匿名转换。 |
34.6. 定义计时器
转换也可以基于定时事件发生。Spring Statemachine 支持两种类型的计时器,一种在 background 和 Ones 在 state 为 进入。
要将新的 TimeEvent 子项添加到 Model Explorer,请将 When 修改为 表达式定义为 LiteralInteger。它的值(以毫秒为单位)成为计时器。 Leave Is Relative false 使计时器连续触发。
要定义一个在进入状态时触发的基于定时的事件,该过程恰好是 与前面描述相同,但将 Is Relative (相对) 设置为 true。下图 显示结果:
然后,用户可以选择这些定时事件之一,而不是 signal 事件。
34.7. 定义一个选择
通过将一个传入过渡绘制到
CHOICE 状态并绘制从该状态到目标的多个传出过渡
国家。我们的配置模型允许您定义
if/elseif/else 结构。但是,对于 UML,我们需要使用
用于传出转换的单个 Guard。StateConfigurer
您必须确保为 transitions 定义的守卫不会重叠,以便 无论发生什么,在任何给定的 guard 中,只有一个守卫的计算结果为 TRUE 时间。这为 choice 分支提供了精确且可预测的结果 评估。此外,我们建议保留一个没有守卫的 transition ,以便保证至少一个过渡路径。 下图显示了使用三个分支进行选择的结果:
Junction 的工作方式类似,只是它允许多个传入 转换。因此,与 Choice 相比,它的行为纯粹是 学术。选择 out-direction transition 的实际 logic 是完全相同的。 |
34.8. 定义 Junction
请参阅 定义选择项。
34.9. 定义 Entry 和 Exit Points
您可以使用 EntryPoint 和 ExitPoint 创建受控的进入和退出
与具有子状态的 state 一起。在下面的状态图中,事件 和 通过进入和退出状态 具有正常状态行为,而正常状态行为通过进入初始状态 发生。E1
E2
S2
S21
Using 事件将机器带入 EntryPoint,然后
导致 NOT ACTIVE INITIAL state 在任何时候。
同样,带有事件的 ExitPoint 控制特定的退出
into state 中,而正常的 exit behavior from 将采用
machine 进入状态 。在 state 上时,您可以选择
事件并使机器进入状态或 ,
分别。下图显示了结果:E3
ENTRY
S22
S21
EXIT
E4
S4
S2
S3
S22
E4
E2
S3
S4
如果 state 被定义为子机引用,并且你需要使用入口点和出口点, 您必须在外部定义一个 ConnectionPointReference,并使用 其 Entry 和 Exit 引用设置为指向正确的 Entry 或 Exit 点 在 Submachine 引用中。只有在那之后,才有可能 定位从外部正确链接到内部的过渡 Sub-machine 引用。使用 ConnectionPointReference,您可能需要 从 Properties → Advanced → UML →查找这些设置 进入/退出。UML 规范允许您定义多个入口和出口。然而 对于状态机,只允许一个。 |
34.10. 定义历史状态
在处理历史状态时,有三个不同的概念在起作用。 UML 定义了 Deep History 和 Shallow History。默认历史记录 当历史状态尚未知时,状态开始发挥作用。这些是 在以下各节中表示。
34.11. 定义 fork 和 join
Fork 和 Join 在 Papyrus 中都表示为条形。如图所示
在下一个图像中,您需要绘制一个从 Into 状态的传出过渡,以具有正交区域。 则相反,其中
从 incoming transitions 中收集 join 状态。FORK
S2
JOIN
34.12. 定义操作
您可以使用行为来关联 Swtate 进入和退出操作。 有关此内容的更多信息,请参见定义 Bean 引用。
34.12.1. 使用初始操作
定义了初始操作(如配置操作中所示) 在 UML 中,通过在转换中添加一个从初始状态引出的操作 marker 添加到实际状态中。然后,当状态 machine 已启动。
34.13. 定义守卫
您可以通过首先添加 Constraint,然后定义 其 Specification 指定为 OpaqueExpression,其工作方式相同 定义为定义 Bean 引用。
34.14. 定义 Bean 引用
当您需要在任何 UML 效果中进行 bean 引用时,
action 或 guard 执行此操作,您可以使用 或 来执行此操作,其中定义的语言需要
be 和语言体 msut 具有 bean 引用 ID。FunctionBehavior
OpaqueBehavior
bean
34.15. 定义 SPEL 引用
当你需要在
任何 UML 效果、操作或守卫,都可以通过使用 或 来实现,其中定义的语言需要
be 并且语言主体必须是 SPEL 表达式。FunctionBehavior
OpaqueBehavior
spel
34.16. 使用 Sub-Machine 引用
通常,当您使用子状态时,您会将它们绘制到 state 中 图表本身。图表可能会变得过于复杂和庞大,以至于无法 follow,因此我们也支持将子 state 定义为 state machine 参考。
要创建子计算机引用,必须首先创建一个新图表并为其命名 (例如,SubStateMachine Diagram)。下图显示了要使用的菜单选项:
为新图表提供您需要的设计。 下图显示了一个简单的设计作为示例:
从要链接的状态(在本例中为 m state )中,单击字段并选择链接的计算机(在我们的示例中为 )。S2
Submachine
SubStateMachine
最后,在下图中,您可以看到 state 被链接为
子状态。S2
SubStateMachine
34.17. 使用机器导入
也可以在 uml 文件可以引用其他模型的地方使用导入功能。
在其中可以使用其他资源或位置
以定义引用模型文件。UmlStateMachineModelFactory
@Configuration
@EnableStateMachine
public static class Config3 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
@Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new UmlStateMachineModelFactory(
"classpath:org/springframework/statemachine/uml/import-main/import-main.uml",
new String[] { "classpath:org/springframework/statemachine/uml/import-sub/import-sub.uml" });
}
}
UML 模型中文件之间的链接需要是相对的,因为 否则,当模型文件从 classpath 添加到临时目录,以便 Eclipse 解析类可以 阅读这些。 |
35. 存储库支持
本节包含与使用 'Spring Data Repositories 的 Spring Statemachine 中。
35.1. 存储库配置
您可以将机器配置保留在外部 storage,可以从中按需加载它,而不是创建静态 配置。这 集成通过 Spring Data Repository 抽象工作。
我们创建了一个特殊的实现
叫。它可以使用 base
存储库接口(、 、 和 )和基本实体
接口 (、 、 和 )。StateMachineModelFactory
RepositoryStateMachineModelFactory
StateRepository
TransitionRepository
ActionRepository
GuardRepository
RepositoryState
RepositoryTransition
RepositoryAction
RepositoryGuard
由于实体和存储库在 Spring Data 中的工作方式,
从用户的角度来看,读取访问可以按原样完全抽象化
完成 。没有必要
了解存储库使用的实际映射实体类。
写入存储库始终依赖于使用实际的
特定于存储库的实体类。从计算机配置点
的观点,我们不需要知道这些,这意味着我们不需要知道
实际实现是 JPA、Redis 还是其他任何东西
Spring Data 支持的。使用实际的存储库相关
实体类在手动尝试编写新的
状态或转换到支持的存储库中。RepositoryStateMachineModelFactory
和 的实体类有一个字段,该字段供您使用,可用于
区分配置 — 例如,如果构建了机器
通过。RepositoryState RepositoryTransition machineId StateMachineFactory |
实际的实现将在后面的部分中记录。 下图是存储库的 UML 等效状态图 配置。
35.1.1. 日JPA
JPA 的实际存储库实现是 、
和 ,它们由
实体类 、 、 和 。JpaStateRepository
JpaTransitionRepository
JpaActionRepository
JpaGuardRepository
JpaRepositoryState
JpaRepositoryTransition
JpaRepositoryAction
JpaRepositoryGuard
不幸的是,版本 '1.2.8' 不得不对 JPA 的实体进行更改
model 的 model 来使用。以前,生成的表名
始终具有从实体类派生的前缀
名字。由于这会导致数据库强加的中断问题
对数据库对象长度的限制,所有实体类都有
spesific definitions 强制使用表名。例如,现在是 'STATE' — 依此类推,与其他
ntity 类。JPA_REPOSITORY_ JPA_REPOSITORY_STATE |
显示了手动更新 JPA 状态和转换的通用方法 在下面的示例中(相当于 SimpleMachine 中所示的机器):
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
以下示例也等效于 SimpleSubMachine 中所示的计算机。
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS22 = new JpaRepositoryState("S22");
stateS22.setParentState(stateS2);
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
stateRepository.save(stateS21);
stateRepository.save(stateS22);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS21, stateS22, "E2");
JpaRepositoryTransition transitionS21ToS22 = new JpaRepositoryTransition(stateS2, stateS3, "E3");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
transitionRepository.save(transitionS21ToS22);
}
首先,您必须访问所有存储库。 以下示例显示了如何执行此操作:
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;
@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;
其次,你可以创建动作和守卫。 以下示例显示了如何执行此操作:
JpaRepositoryGuard foo0Guard = new JpaRepositoryGuard();
foo0Guard.setName("foo0Guard");
JpaRepositoryGuard foo1Guard = new JpaRepositoryGuard();
foo1Guard.setName("foo1Guard");
JpaRepositoryAction fooAction = new JpaRepositoryAction();
fooAction.setName("fooAction");
guardRepository.save(foo0Guard);
guardRepository.save(foo1Guard);
actionRepository.save(fooAction);
第三,您必须创建状态。 以下示例显示了如何执行此操作:
JpaRepositoryState stateS0 = new JpaRepositoryState("S0", true);
stateS0.setInitialAction(fooAction);
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
stateS1.setParentState(stateS0);
JpaRepositoryState stateS11 = new JpaRepositoryState("S11", true);
stateS11.setParentState(stateS1);
JpaRepositoryState stateS12 = new JpaRepositoryState("S12");
stateS12.setParentState(stateS1);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
stateS2.setParentState(stateS0);
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS211 = new JpaRepositoryState("S211", true);
stateS211.setParentState(stateS21);
JpaRepositoryState stateS212 = new JpaRepositoryState("S212");
stateS212.setParentState(stateS21);
stateRepository.save(stateS0);
stateRepository.save(stateS1);
stateRepository.save(stateS11);
stateRepository.save(stateS12);
stateRepository.save(stateS2);
stateRepository.save(stateS21);
stateRepository.save(stateS211);
stateRepository.save(stateS212);
第四,也是最后一点,您必须创建过渡。 以下示例显示了如何执行此操作:
JpaRepositoryTransition transitionS1ToS1 = new JpaRepositoryTransition(stateS1, stateS1, "A");
transitionS1ToS1.setGuard(foo1Guard);
JpaRepositoryTransition transitionS1ToS11 = new JpaRepositoryTransition(stateS1, stateS11, "B");
JpaRepositoryTransition transitionS21ToS211 = new JpaRepositoryTransition(stateS21, stateS211, "B");
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "C");
JpaRepositoryTransition transitionS1ToS0 = new JpaRepositoryTransition(stateS1, stateS0, "D");
JpaRepositoryTransition transitionS211ToS21 = new JpaRepositoryTransition(stateS211, stateS21, "D");
JpaRepositoryTransition transitionS0ToS211 = new JpaRepositoryTransition(stateS0, stateS211, "E");
JpaRepositoryTransition transitionS1ToS211 = new JpaRepositoryTransition(stateS1, stateS211, "F");
JpaRepositoryTransition transitionS2ToS21 = new JpaRepositoryTransition(stateS2, stateS21, "F");
JpaRepositoryTransition transitionS11ToS211 = new JpaRepositoryTransition(stateS11, stateS211, "G");
JpaRepositoryTransition transitionS0 = new JpaRepositoryTransition(stateS0, stateS0, "H");
transitionS0.setKind(TransitionKind.INTERNAL);
transitionS0.setGuard(foo0Guard);
transitionS0.setActions(new HashSet<>(Arrays.asList(fooAction)));
JpaRepositoryTransition transitionS1 = new JpaRepositoryTransition(stateS1, stateS1, "H");
transitionS1.setKind(TransitionKind.INTERNAL);
JpaRepositoryTransition transitionS2 = new JpaRepositoryTransition(stateS2, stateS2, "H");
transitionS2.setKind(TransitionKind.INTERNAL);
transitionS2.setGuard(foo1Guard);
transitionS2.setActions(new HashSet<>(Arrays.asList(fooAction)));
JpaRepositoryTransition transitionS11ToS12 = new JpaRepositoryTransition(stateS11, stateS12, "I");
JpaRepositoryTransition transitionS12ToS212 = new JpaRepositoryTransition(stateS12, stateS212, "I");
JpaRepositoryTransition transitionS211ToS12 = new JpaRepositoryTransition(stateS211, stateS12, "I");
JpaRepositoryTransition transitionS11 = new JpaRepositoryTransition(stateS11, stateS11, "J");
JpaRepositoryTransition transitionS2ToS1 = new JpaRepositoryTransition(stateS2, stateS1, "K");
transitionRepository.save(transitionS1ToS1);
transitionRepository.save(transitionS1ToS11);
transitionRepository.save(transitionS21ToS211);
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS1ToS0);
transitionRepository.save(transitionS211ToS21);
transitionRepository.save(transitionS0ToS211);
transitionRepository.save(transitionS1ToS211);
transitionRepository.save(transitionS2ToS21);
transitionRepository.save(transitionS11ToS211);
transitionRepository.save(transitionS0);
transitionRepository.save(transitionS1);
transitionRepository.save(transitionS2);
transitionRepository.save(transitionS11ToS12);
transitionRepository.save(transitionS12ToS212);
transitionRepository.save(transitionS211ToS12);
transitionRepository.save(transitionS11);
transitionRepository.save(transitionS2ToS1);
您可以在此处找到完整的示例。此示例还演示了如何 从具有 实体类的定义。
35.1.2. Redis
Redis 实例的实际存储库实现是 、
和 ,它们由
实体类 、 、 和 。RedisStateRepository
RedisTransitionRepository
RedisActionRepository
RedisGuardRepository
RedisRepositoryState
RedisRepositoryTransition
RedisRepositoryAction
RedisRepositoryGuard
下一个示例显示了手动更新 Redis 状态和过渡的通用方法。 这相当于 SimpleMachine 中所示的机器。
@Autowired
StateRepository<RedisRepositoryState> stateRepository;
@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;
void addConfig() {
RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
RedisRepositoryState stateS3 = new RedisRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
以下示例等效于 SimpleSubMachine 中显示的 machine:
@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 实例的实际存储库实现是 、
和 ,它们由
实体类 、 、 和 。MongoDbStateRepository
MongoDbTransitionRepository
MongoDbActionRepository
MongoDbGuardRepository
MongoDbRepositoryState
MongoDbRepositoryTransition
MongoDbRepositoryAction
MongoDbRepositoryGuard
下一个示例显示了手动更新 MongoDB 的状态和转换的通用方法。 这相当于 SimpleMachine 中所示的机器。
@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;
@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;
void addConfig() {
MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
以下示例等效于 SimpleSubMachine 中所示的计算机。
@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;
@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;
void addConfig() {
MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");
MongoDbRepositoryState stateS21 = new MongoDbRepositoryState("S21", true);
stateS21.setParentState(stateS2);
MongoDbRepositoryState stateS22 = new MongoDbRepositoryState("S22");
stateS22.setParentState(stateS2);
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
stateRepository.save(stateS21);
stateRepository.save(stateS22);
MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS21, stateS22, "E2");
MongoDbRepositoryTransition transitionS21ToS22 = new MongoDbRepositoryTransition(stateS2, stateS3, "E3");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
transitionRepository.save(transitionS21ToS22);
}
35.2. 存储库持久性
除了将机器配置(如仓库配置中所示)存储在外部仓库中外,您还可以 将计算机持久保存到存储库中。
该接口是一个中央接入点,它
与机器持久互,并由 Entity 类提供支持。StateMachineRepository
RepositoryStateMachine
35.2.1. 日式分析
JPA 的实际存储库实现是 ,它由 entity 类 提供支持。JpaStateMachineRepository
JpaRepositoryStateMachine
以下示例显示了为 JPA 保留计算机的通用方法:
@Autowired
StateMachineRepository<JpaRepositoryStateMachine> stateMachineRepository;
void persist() {
JpaRepositoryStateMachine machine = new JpaRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}
35.2.2. Redis 餐厅
Redis 的实际存储库实现是 ,它由 entity 类 提供支持。RedisStateMachineRepository
RedisRepositoryStateMachine
以下示例显示了为 Redis 保留计算机的通用方法:
@Autowired
StateMachineRepository<RedisRepositoryStateMachine> stateMachineRepository;
void persist() {
RedisRepositoryStateMachine machine = new RedisRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}
35.2.3. MongoDB 数据库
MongoDB 的实际存储库实现是 ,它由 entity class 提供支持。MongoDbStateMachineRepository
MongoDbRepositoryStateMachine
以下示例显示了为 MongoDB 持久保存计算机的通用方法:
@Autowired
StateMachineRepository<MongoDbRepositoryStateMachine> stateMachineRepository;
void persist() {
MongoDbRepositoryStateMachine machine = new MongoDbRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}