11. 状态机配置
使用Statemachine时的一个常见任务是设计其运行时配置。 本章将重点介绍如何配置Spring Statemachine以及如何利用Spring轻量级IoC容器来简化应用程序内部以使其更易于管理。
本节中的配置示例不是功能完整的,即您始终需要具有状态和转换的定义,否则状态机配置将不合格。 我们只是通过将其他需要的部分留下来而简化了代码片段。
11.1 使用enable注解
我们使用熟悉的spring enabler注释来简化配置。 有两个注解,@EnableStateMachine和@EnableStateMachineFactory。 这些注解如果放置在@Configuration类中,将启用状态机所需的一些基本功能。
当配置想要创建一个StateMachine的实例时使用@EnableStateMachine。 通常@Configuration类扩展了适配器EnumStateMachineConfigurerAdapter或StateMachineConfigurerAdapter,它允许用户覆盖配置回调方法。 我们会自动检测用户是否正在使用这些适配器类并修改运行时配置逻辑。
当配置想要创建StateMachineFactory的一个实例时使用@EnableStateMachineFactory。
以下部分显示了这些使用示例。
11.2 配置状态
稍后我们将介绍更复杂的配置示例,但我们首先从简单的事情开始。 对于最简单的状态机,只需使用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));
}
}
通过使用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")));
}
}
使用枚举将带来更安全的状态和事件类型集,但会限制可能的组合来编译时间。 字符串没有这种限制,并允许用户使用更动态的方式来构建状态机配置,但不允许相同的安全级别。
11.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);
}
}
11.4 配置区域
没有特殊的配置方法可以将一组状态标记为正交状态的一部分。 简单地说,当相同的分层状态机具有多组具有初始状态的状态时,创建正交状态。 因为单个状态机只能有一个初始状态,所以多个初始状态必定意味着一个特定的状态必须有多个独立的区域。
@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);
}
}
11.5 配置转换
我们支持三种不同类型的过渡,包括外部,内部和本地。 转换或者是由一个信号触发的,该信号是发送到状态机或计时器的事件。
@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);
}
}
11.6 配置防护(Guard)
防护(Guard)被用来保护状态转换。 Interface 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被创建为一个bean并附加到状态S1和S2之间的转换。
其次,一个简单的SPeL表达式可以用作表达式必须返回布尔值的Guard。 在幕后,这种基于表情的警卫是SpelExpressionGuard。 这与状态S2和S3之间的转换有关。 上面的示例中的守卫总是评估为真
11.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
}
};
}
}
在上面,单个Action被定义为bean操作并且与从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实例,但我们在这里做的不是在代码片段中做太多的噪音。
在上面,单个Action被定义为bean操作并与状态S1,S2和S3关联。 还有更多的事情需要更多的澄清:
- 我们定义了初始状态S1的动作。
- 我们为状态S1定义了进入动作,并且退出动作为空。
- 我们为状态S2定义了退出动作,并且将进入动作留空。
- 我们为状态S2定义了一个单独的状态动作。
- 我们为状态S3定义了一个单进入动作和退出动作
- 注意状态S1是如何在initial()和state()函数中使用两次的。 这只有在你想用初始状态定义进入或退出动作时才需要。
重 要
使用initial()函数定义动作只会在状态机或子状态启动时执行特定动作。 将此操作视为初始化仅执行一次的操作。 如果状态机在初始状态和非初始状态之间前后转换,则执行使用state()定义的动作。
11.7.1状态动作
与进入和退出操作相比,状态操作的执行方式不同,只是因为执行是在进入状态之后发生的,并且如果在特定操作完成之前发生状态退出,则可以取消执行。
状态操作是使用包装在Runnable中的普通Spring TaskScheduler来执行的,可以通过ScheduledFuture取消。 这意味着无论你在动作中做什么,都需要能够捕获InterruptedException或者通常定期检查线程是否被中断。
下面显示了典型的配置,它使用默认的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");
}
}
可以将策略设置为TIMEOUT_CANCEL以及每台计算机的全局超时。 这会将状态更改为在请求取消之前等待操作完成。
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
.stateDoActionPolicyTimeout(10, TimeUnit.SECONDS);
}
如果事件直接将状态机置于某个状态,以便事件标题可用于特定操作,则还可以使用专用事件标题指示以毫秒定义的特定超时。 保留的标题值StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT用于此。
@Autowired
StateMachine<String, String> stateMachine;
void sendEventUsingTimeout() {
stateMachine.sendEvent(MessageBuilder
.withPayload("E1")
.setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
.build());
}
11.7.2 转换动作错误处理
用户总是可以手动捕捉异常,但是为转换定义的动作可以定义在引发异常时调用的错误操作。 然后可以从传递给该操作的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();
}
};
}
}
类似的逻辑可以根据需要手动完成每个动作。
@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()));
}
11.7.3状态动作错误处理
类似的错误处理逻辑可用于转换操作,也可用于为状态行为及其进入和退出定义的操作。
对于这些StateConfigurer有方法stateEntry,stateDo和stateExit来定义错误操作和实际操作。
@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();
}
};
}
}
11.8 8 配置伪状态
伪状态配置通常通过配置状态和转换完成。 伪状态会自动添加到状态机中作为状态。
11.8.1初始状态
只需使用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
}
};
}
}
11.8.2 终止状态
只需使用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));
}
}
11.8.3 历史状态
历史状态可以为每个单独的状态机定义一次。 您需要分别选择其状态标识符和History.SHALLOW或History.DEEP。
@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);
}
}
同样如上所示,可选地,可以在同一机器中定义从历史状态到状态顶点的默认转换。 如果例如机器从未被输入,则该转换作为默认进行,因此没有历史记录可用。 如果未定义默认状态转换,则正常进入区域完成。 如果机器的历史记录是最终状态,也会使用此默认转换。
11.8.4 可选状态
选择需要在两种状态和转换中定义才能正常工作。 使用choice()方法将特定状态标记为选择状态。 当为此选择配置转换时,此状态需要匹配源状态。
使用withChoice()配置转换,您可以在其中定义源状态和 first/then/last 结构,该结构等同于正常情况下的 if/elseif/else。 首先,然后你可以像使用 if/elseif 子句中的条件一样指定一个guard。
过渡需要能够存在,所以确保使用最后一个。 否则配置不合格。
@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;
}
};
}
}
动作可以通过选择伪状态的传入和传出转换来执行。 从下面的例子可以看出,一个虚拟lambda动作被定义为一个选择状态,一个类似的虚拟lambda动作被定义为一个传出转换,它也定义了一个错误动作。
@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
});
}
}
连接点具有相同的api格式,可以类似地定义动作。
11.8.5 连接状态
连接点需要在两种状态和转换中定义才能正常工作。 使用junction()方法将特定状态标记为选择状态。 当为此选择配置转换时,此状态需要匹配源状态。
使用withJunction()配置转换,您可以在其中定义源状态和first / then / last结构,该结构等同于正常情况下的/ elseif / else。 首先,然后你可以像使用if / elseif子句中的条件一样指定一个警卫。
过渡需要能够存在,所以确保使用最后一个。 否则配置不合格。
@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;
}
};
}
}
选择和交叉点之间的区别纯粹是学术的,因为两者都是用first / then / last结构实现的。 然而在理论上基于uml模型,选择只允许一个传入转换,而联合允许多个传入转换。 在代码级别,功能几乎完全相同。
11.8.6 分支状态
分支需要在两个状态和转换中定义才能正常工作。 使用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);
}
}
11.8.7 加入状态
加入需要在两种状态和转换中定义才能正常工作。 使用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);
}
}
也可能有多个来自联接状态的转换。 在这种情况下,建议使用警卫并对其进行定义,以便在任何给定的时间只有一名guard评估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()");
}
}
11.8.8 退出/入口点状态
出口和入口点可用于进行更多受控制的出入口进出子机。
@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");
}
}
如上所示,您需要将特定状态标记为退出和输入状态。 然后你创建一个正常的转换到这些状态,并且还指定withExit()和withEntry()这些状态将分别退出和输入。
11.9 配置常用设置
一些常见的状态机配置可以通过ConfigurationConfigurer进行设置。 这允许为状态机设置BeanFactory,TaskExecutor,TaskScheduler,自动启动标志并注册StateMachineListener实例。
@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())
.taskExecutor(new SyncTaskExecutor())
.taskScheduler(new ConcurrentTaskScheduler())
.listener(new StateMachineListenerAdapter<States, Events>())
.transitionConflictPolicy(TransitionConflictPolicy.CHILD);
}
}
状态机autoStartup标志在默认情况下是禁用的,因为所有处理子状态的实例都是由状态机本身控制的,不能自动启动。 对于用户是否应该自动启动机器,将此决定留给用户更安全。 该标志只会控制顶层状态机的自动启动。
如果用户想要或需要在此处配置machineId,那么在配置中设置machineId非常方便。
为了方便用户而设置BeanFactory,TaskExecutor或TaskScheduler,并且在框架本身中也可以使用。
注册StateMachineListener实例也是为了方便,但如果用户想要在状态机生命周期中捕获回调,例如获取状态机启动/停止事件的通知,则需要注册StateMachineListener实例。 当然,如果启用了autoStartup,除非可以在配置阶段注册监听器,否则不可能监听状态机启动事件。
在可以选择多个转换路径的情况下,可以使用transitionConflictPolicy。 一个常见的用例是,如果机器包含从子状态和父状态引出的匿名转换,并且用户想要定义将选择哪一个的策略。 这是机器实例中的全局设置,默认为CHILD。
DistributedStateMachine是通过withDistributed()配置的,它允许设置一个StateMachineEnsemble,如果该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;
}
}
有关分布式状态的更多信息,请参见第31章,使用分布式状态。
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
}
};
}
}
More about config model, refer to section Section 55.1, “StateMachine Config Model”
.配置方法withSecurity,withMonitoring和withPersistence分别在章节25,状态机安全性,第30章监控状态机和第28.4节“使用StateMachineRuntimePersister”中进行了介绍。
11.10 配置模型
StateMachineModelFactory是一个挂钩,可以在不使用手动配置的情况下配置statemachine模型。 从本质上来说,它是一个集成到配置模型中的第三方集成。 通过使用StateMachineModelConfigurer可以将StateMachineModelFactory挂钩到配置模型中,如上所示。
@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();
}
}
定义自定义模型通常不是最终用户所期望的,尽管这是可能的,但它是允许外部访问此配置模型的核心概念。
使用此模型工厂集成的示例可以从第33章Eclipse Modeling支持中找到。 有关自定义模型集成的更多通用信息,请参阅第55章开发人员文档。
11.11 要记住的事情
在定义动作,guard或配置的任何其他引用时,需要记住Spring Framework如何与bean一起工作。 在下面,我们定义了状态为S1和S2的正常配置,并在这些状态之间进行了4次转换。 所有的转换都由guard1或guard2来保护。 注意guard1被创建为一个真正的bean,因为它用@Bean注释,而guard2不是。
这意味着事件E3会将guard2条件设置为TRUE,并且E4会将guard2条件设置为FALSE,因为这些条件仅仅是来自对这些函数的简单方法调用。
但是因为guard1被定义为一个@Bean,所以它被Spring框架代理,因此对其方法的额外调用只会导致该实例的一个实例化。 事件E1将获得条件为TRUE的第一个代理实例,而事件E2将在TRUE条件下获得相同的实例,而方法调用被定义为FALSE。 这不是Spring State Machine特有的行为,它只是Spring框架如何与Bean一起工作。
@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;
}
};
}
}