使用 Spring 状态机
参考文档的这一部分解释了核心功能 Spring状态机为任何基于Spring的应用程序提供这些功能。
课程涵盖以下主题:
-
状态机配置描述了通用配置支持。
-
状态机ID描述了机器ID的使用方式。
-
State Machine Factories 描述了通用的状态机工厂支持。
-
使用延期事件描述了延期事件的支持。
-
使用Scopes描述了Scope支持。
-
使用动作描述了动作的支持。
-
使用守卫描述了守卫支援。
-
使用扩展状态描述了扩展状态支持。
-
用
StateContext描述状态上下文支持。 -
触发过渡描述了触发器的使用。
-
监听状态机事件描述了状态机监听器的使用。
-
上下文集成描述了通用的 Spring 应用上下文支持。
-
用
StateMachineAccessor描述状态机内部访问器支持。 -
用
状态机拦截者描述状态机错误处理支持。 -
状态机安全描述了状态机安全支持。
-
状态机错误处理描述了对状态机拦截器的支持。
-
状态机服务描述了状态机服务的支持。
-
持久化状态机描述了对持久化的状态机支持。
-
Spring Boot 支持描述了 Spring Boot 的支持。
-
监控状态机描述了监控和追踪支持。
-
使用分布式状态描述了分布式状态机的支持。
-
测试支持描述状态机测试支持。
-
Eclipse 建模支持描述了状态机 UML 建模支持。
-
仓库支持描述了状态机仓库配置支持。
12. 状态机配置
使用状态机时,常见的任务之一是设计其 运行时配置。本章重点讲述Spring如何 状态机的配置及其如何利用Spring的轻量化功能 IoC 容器简化应用内部结构,使其更为简单 管理。
| 本节中的配置示例并非完整的功能。那是 你总是需要对状态和转移都有定义。 否则,状态机配置将不成熟。我们有 简单来说,通过保留其他必要的部分,使代码片段变得不那么冗长 外。 |
12.1. 使用使附注
我们使用两个熟悉的 Spring enabler 注释来简化配置:@EnableStateMachine和@EnableStateMachineFactory.
这些注释放在@Configuration类,使得
状态机所需的一些基本功能。
你可以使用@EnableStateMachine当你需要配置来创建
实例状态机.通常,@Configuration类扩展适配器
(EnumStateMachineConfigurerAdapter或StateMachineConfigurerAdapter),其中
允许你覆盖配置回调方法。我们自动
检测你是否使用这些适配器类并修改运行时配置
逻辑上也得以理解。
你可以使用@EnableStateMachineFactory当你需要配置来创建
一个实例状态机工厂.
| 这些产品的使用示例见下文章节。 |
12.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")));
}
}
| 使用枚举可以带来更安全的状态和事件类型,但 限制了编译时间内的组合。弦没有这个功能 限制,让你可以用更动态的方式来构建状态 但无法实现同等的安全性。 |
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. 配置区域
没有特殊的配置方法来标记一组 状态被认为属于一个正交状态。简单来说,就是正交的 当同一层级状态机拥有多个集合时,状态就被创造出来 每个状态都有初始状态。因为一个独立的国家 机器只能有一个初始状态,多个初始状态必须存在 意味着一个特定州必须拥有多个独立的地区。 以下示例展示了如何定义区域:
@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。如下例子所示,StateConfigurer具有
一种称为区域(字符串ID)这样你就可以为某个区域设置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. 配置过渡
我们支持三种不同类型的过渡:外部,内部和当地.跃迁可由信号触发
(即发送到状态机的事件)或通过计时器。
以下示例展示了如何定义这三种转移:
@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. 配置守卫
你可以用守卫来保护状态转换。你可以使用警卫接口
当方法能够访问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;
}
};
}
}
在前面的例子中,我们使用了两种不同的保护配置。首先,我们
创造了一个简单的警卫作为豆子,并将其连接到
国家第一季和第二季.
其次,我们使用SPeL表达式作为守护词来表示
表达式必须返回一个布尔价值。幕后,这个
基于表达式的守卫是一种斯佩尔表情守卫.我们把它附加在
状态之间的过渡第二季和第三季.两名守卫
总是将值定为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
}
};
}
}
在前面的例子中,单个行动被定义为一个名为行动并与之相关
通过一个从第一季自第二季.
以下示例展示了如何多次使用一个动作:
@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
}
};
}
}
通常,你不会给出相同的定义行动实例 for different
但我们在这里这样做,是为了避免代码中太吵
片段。 |
在前面的例子中,单个行动由名为的豆子定义行动并与之相关
与州第一季,第二季和第三季.我们需要澄清这里发生了什么:
-
我们定义了一个初始状态的作用,
第一季. -
我们定义了状态的入口动作
第一季并留下了退出行动。 -
我们定义了状态的退出作用
第二季并保持进入动作为空。 -
我们为国家定义了单一的州行动
第二季. -
我们定义了状态的进入和退出动作
第三季. -
注意该状态
第一季在 中被使用两次initial()和state()功能。只有当你想定义进入或退出时,才需要这样做 具有初始状态的动作。
定义作用量initial()函数只运行特定的
当状态机或子状态启动时,作。此举
是一个只执行一次的初始化动作。一个定义的动作
跟state()如果状态机切换回去,则运行
以及初始态和非初始态之间的前进。 |
12.7.1. 州级行动
状态行动的执行方式与进入和退出不同 动作,因为执行是在状态输入后才进行的 如果状态退出发生在特定动作之前,则可以取消 已完工。
状态动作通过订阅反应器的默认并行调度器来执行,使用正常响应式流程。这意味着无论你在动作中做什么,都需要能够捕捉中断异常或者,更一般地,定期检查线被打断了。
以下示例展示了典型配置,使用默认配置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(Mono.just(MessageBuilder
.withPayload("E1")
.setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
.build()))
.subscribe();
}
12.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()));
}
12.7.3. 状态动作错误处理
类似于处理状态转换错误的逻辑也存在用于进入状态和退出状态。
对于这些情况,StateConfigurer有 的方法称为州条目,stateDo和州退出. 这些方法定义了错误动作与正常(非错误)行动. 以下示例展示了如何使用这三种方法:
@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. 配置伪态
伪状态配置通常通过配置状态和 转换。 伪态会自动添加到状态机中,表示为 国家。
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. 终止状态
你可以通过使用结束()方法。 你最多可以对每个子机或区域进行一次作。以下示例展示了如何使用结束()方法:
@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. 州历史
你可以为每个单独的状态机定义一次状态历史。你需要选择它的状态标识符并设置以下任一历史。浅薄或历史。深层. 以下示例使用历史。浅薄:
@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);
}
}
此外,如前例所示,你可以选择性地定义一个默认从历史状态到状态顶点的转换。该转换作为默认发生,例如,如果机器从未进入——因此,不会有历史记录。如果没有定义默认状态状态转换,那么正常进入某个区域的 做。 当机器的历史是最终状态时,也会使用这种默认转换。
12.8.4. 选择州
选择需要在状态和工作过渡中定义 适当地。 你可以通过以下方式将某个状态标记为选择状态选择()方法。 当转换进行为此选择配置时,该状态需要与源状态匹配。
你可以通过以下方式配置过渡withChoice(),定义源状态和第一/然后/最后结构,等价于一个法如果/否则/否则.跟第一和然后,你可以仅指定一个守卫就像你会用条件如果/否则第。
过渡必须能够存在,所以你必须确保使用最后. 否则,配置是错形的。以下示例展示了如何定义选择态:
@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 格式,意味着可以定义动作 同样地。 |
12.8.5. 交汇州
你需要在状态和转移中都定义一个连接,这样才能工作
适当地。你可以通过使用交汇点()方法。当转移为
为此进行了配置。
你可以通过以下方式配置过渡withJunction()你定义了源
状态和第一/然后/最后结构(等价于法线如果/否则/否则).跟第一和然后,你可以指定守卫为
你会使用条件如果/否则第。
过渡必须能够存在,所以你必须确保使用最后.
否则,构型就不完整。
以下示例使用了一个交汇点:
@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;
}
};
}
}
选择和交汇的区别纯属学术层面,因为两者都是
实现方式如下第一/然后/最后结构。然而,理论上,
关于UML建模,选择只允许一个入射转移,而结允许多个输入转换。在代码层面,
功能基本一样。 |
12.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);
}
}
12.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);
}
}
你也可以让多个转换源自
加入州政府。在这种情况下,我们建议你使用护卫并明确你的护卫
使得只有一个守卫值为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. 出入口状态
你可以利用出入口点来实现更受控的出入口
从潜艇中进入。
以下示例使用了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");
}
}
如前所述,你需要将特定状态标记为退出和进入国家。然后你创建一个正常的跃迁到这些状态
并且还具体指定了withExit()和withEntry(),其中
分别是出境和进入。
12.9. 配置通用设置
你可以通过以下方式设置部分通用状态机配置ConfigurationConfigurer.用它你可以设置豆子工厂以及自动启动标志
对于一个状态机来说。它还允许你注册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);
}
}
默认情况下,状态机自动启动旗标被禁用,因为所有
处理子状态的实例由状态机本身控制
不能自动启动。而且离开会安全得多
是否应该启动机器
自动或不自动发送给用户。该标志仅控制 的自动启动
顶级状态机。
设置machineId在配置类中,仅仅是为了方便在
你想或者需要在那里做。
注册StateMachineListener实例数也部分为
方便,但如果你想在
状态机生命周期,例如收到状态机的通知
活动的开始和停止。注意你不能监听一个状态
机器的启动事件 如果自动启动是启用的,除非你注册了监听者
在配置阶段。
你可以使用过渡冲突政策当多重时
可以选择过渡路径。一个常见的用例是当
machine 包含从子状态导出的匿名转移
还有一个父国,你想定义一个政策,其中一个
选择。这是机器实例内的全局设置,
默认为孩子.
你可以使用withDistributed()配置分布式状态机.它
允许你设置StateMachineEnsemble,如果存在的话,自动生成
包裹任何创建的状态机跟分布式状态机和
启用分布式模式。以下示例展示了如何使用它:
@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;
}
}
关于分布式状态的更多信息,请参见《使用分布式状态》。
这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
}
};
}
}
关于配置模型的更多信息,请参见状态机配置模型。
这与安全,与监控和坚持不懈配置方法
在状态机安全、状态机监控和用StateMachineRuntimePersister分别。 |
12.10. 配置模型
StateMachineModelFactory是一个钩子,可以让你配置状态机模型
无需手动配置。本质上,它是一个第三方
集成以集成到配置模型中。
你可以钩住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自
定义两个状态(第一季和第二季)以及一个事件(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 建模支持中找到使用这种模型工厂集成的示例。你可以找到更多关于自定义模型集成的通用信息 在开发者文档中。
12.11. 值得铭记的事情
在定义动作、守卫或来自
配置时,记住 Spring Framework 的工作原理很重要
加豆子。在下一个例子中,我们定义了一个正态构型,满足
国家第一季和第二季以及两者之间的四个过渡。所有过渡
由以下两者守护警卫1或守卫2.你必须确保警卫1作为实豆被创建,因为它被注释为@Bean而守卫2莫。
这意味着事件E3会得到守卫2条件为true和E4会得到守卫2条件为false,因为
这些函数来自普通方法调用。
然而,因为警卫1定义为@Bean,它由
Spring Framework。因此,对其方法的额外调用导致
只有该实例的一次实例实例。事件E1首先会得到
带条件的代理实例true,虽然事件E2结果也一样
实例true当方法调用定义为false.这不是 Spring State Machine 特有的行为。相反,它确实如此
Spring Framework 如何与豆子合作。
以下示例展示了该安排的工作原理:
@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
各种类和接口使用machineId无论是作为变量还是
方法中的参数。本节将深入探讨其具体情况machineId与正常机器运行和实例化有关。
运行时,amachineId其实没有什么大运营
除了区分机器之间的其他功能外——例如,当
跟踪日志或做更深入的调试。有很多不同的选择
机器实例很快就会让开发者在翻译中迷失
没有简单的方法来识别这些情况。因此,我们增加了设置machineId.
13.1. 使用@EnableStateMachine
设置machineId在 Java 配置中为我的机器然后暴露出该值
用来做日志。同样如此machineId也可从以下渠道获得StateMachine.getId()方法。以下示例使用了machineId方法:
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.machineId("mymachine");
}
以下对数输出示例展示了我的机器ID:
11:23:54,509 INFO main support.LifecycleObjectSupport [main] -
started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine
| 手动构建器(参见通过构建器的状态机)使用相同的配置 界面,意味着行为是等价的。 |
13.2. 使用@EnableStateMachineFactory
你也能看到同样的情况machineId如果你使用了一个状态机工厂并通过使用该 ID 请求新机器,
如下示例所示:
StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
StateMachine<String, String> machine = factory.getStateMachine("mymachine");
13.3. 使用StateMachineModelFactory
在幕后,所有机器配置首先被转换为StateMachineModel因此状态机工厂不必知道
从构型起源处开始,作为机器的构建来源
Java 配置、UML 或仓库。如果你想疯狂一点,也可以用自定义StateMachineModel,这是最低可能的
定义配置的层级。
这些和machineId?StateMachineModelFactory也有一个带有以下签名的方法:StateMachineModel<S, E> build(String machineId)其中一个StateMachineModelFactory实施可以选择使用。
RepositoryStateMachineModelFactory(参见仓库支持)用途machineId支持持久化中的不同配置
通过 Spring Data Repository 接口存储。例如,两者都适用StateRepository和过渡仓库拥有一个方法 (List<T>
findByMachineId(String machineId)),以构建不同的状态和
通过machineId.跟RepositoryStateMachineModelFactory如果machineId被用作空
或 NULL,默认为仓库配置(在支持持久模型中)
没有已知的机器编号。
现在UmlStateMachineModelFactory不区分
不同的机器ID,因为UML源总是来自同一个
文件。未来版本可能会有所变化。 |
14. 国家机器工厂
有些用例需要动态创建状态机 而不是在编译时定义静态配置。例如 如果存在使用自身状态机的自定义组件 这些组件是动态创建的,不可能 一个在应用启动时构建的静态状态机。内部 状态机总是通过工厂接口构建的。那么 给你一个选项,可以编程使用这个功能。 状态机工厂的配置与图中完全相同 在本文档中的多个例子中,状态机配置 是硬编码的。
14.1. 通过适配器实现出厂
实际创建状态机的方法:@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建立工厂
你可以注入它,而不是状态机 BEAN,然后直接用它到
请求新的状态机。以下示例展示了如何实现:
public class Bean3 {
@Autowired
StateMachineFactory<States, Events> factory;
void method() {
StateMachine<States,Events> stateMachine = factory.getStateMachine();
stateMachine.startReactively().subscribe();
}
}
14.2. 通过构建器实现状态机
使用适配器(如上所示)有一个限制,即其
春季工作要求@Configuration类别和
应用上下文。虽然这是一个非常清晰的模型来配置
状态机,它在编译时限制配置,
但这并不总是用户想要的。如果有需求
要构建更动态的状态机,可以使用简单的构建模式
构建类似实例。通过使用字符串作为状态和
事件,你可以用这个构建模式来构建完全动态的状态
Spring应用上下文之外的机器。以下示例
展示了如何做到这一点:
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();
}
构建者在幕后使用相同的配置接口 这@Configuration模型用于适配器类。同一模型用于通过构建者配置转移、状态和常见配置 方法。 这意味着你能用普通武器的话EnumStateMachineConfigurerAdapter或StateMachineConfigurerAdapter你可以通过构建器动态使用。
目前,builder.configureStates(),builder.configureTransitions(), 和builder.configureConfiguration()接口方法不能串联在一起,这意味着构建方法需要单独调用。 |
以下示例为构建者设置了若干选项:
StateMachine<String, String> buildMachine2() throws Exception {
Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.autoStartup(false)
.beanFactory(null)
.listener(null);
return builder.build();
}
你需要明白什么时候常见配置需要用于从构建者实例化的机器。你可以使用配置器返回于withConfiguration()准备自动启动和豆子工厂. 你也可以用它来注册StateMachineListener. 如果状态机从构建者返回的实例通过以下方式注册为豆子@Bean,豆子工厂是自动附加的。如果你在 Spring 应用上下文之外使用实例,你必须使用这些方法来设置所需的设施。
15. 使用延迟事件
当发送事件时,可能会触发事件触发器,这可能导致如果状态机处于触发条件的状态,会发生成功评估触发。通常,这可能导致事件未被接受而被放弃。然而,你可能希望推迟该事件,直到状态机进入另一个状态。在这种情况下,你可以接受该事件。换句话说,事件出现在一个不便的时间。
Spring状态机提供了延迟事件以备后处理的机制 加工。 每个状态都可以有一个延迟事件列表。如果当前状态的延迟事件列表中发生了事件,该事件会被保存(延迟)以备将来处理,直到输入一个状态不列出该事件在其延迟事件列表中。当进入这样的状态时,状态机会自动调用任何已保存的事件,这些事件不再延迟,然后要么消耗或丢弃这些事件。有可能一个超状态可以定义一个转移,而该事件被延迟由子状态。遵循相同的层级状态机概念,子状态优先于超状态,事件被延迟,且超状态的转移不运行。对于正交区域,其中一个正交区域推迟事件,另一个接受事件,接受优先,事件被消耗而非延迟。
事件延迟最明显的用例是当事件导致进入特定状态,状态机随后返回回到其原始状态,第二个事件应引起同样的反应 过渡。 以下示例展示了这种情况:
@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");
}
}
在前面的例子中,状态机的状态为准备,这表明机器已准备好处理将它带入部署状态,其中实际部署将发生。部署作运行后,机器返回到准备州。 发送多个事件准备如果机器使用同步执行程序,状态不会引起任何问题,因为事件发送会在事件调用之间阻塞。然而,如果执行器使用线程,其他事件可能会丢失,因为机器不再处于事件可以被处理的状态。因此,推迟其中一些事件可以让机器保留它们。以下示例展示了如何配置这种安排:
@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");
}
}
在上述例子中,状态机使用嵌套状态代替平坦状态模型,因此部署事件可以直接在子状态中被延迟。它还展示了延迟的概念做事件发生在子状态中,随后覆盖了匿名的转换 这部署和做表示状态机恰好处于部署准备当做事件已发出。 在部署执行当做活动不会被推迟,这个活动会被推迟
被处理得非常特别。
16. 使用瞄准镜
状态机对示波器的支持非常有限,但你可以
使会期通过使用普通Spring实现示波器@Scope注释方式有两种:
-
如果状态机是通过构建器手动构建的,然后返回到 作为
@Bean. -
通过配置适配器。
两者
这些需要@Scope当范围名称设置为会期和代理模式设置为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");
}
}
提示:请参阅“会话范围”了解如何使用会话范围。
一旦你对状态机进行了范围会期,将其自动接线为
一个@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();
}
}
在会期范围需要仔细规划,
主要是因为它成分相对较重。 |
| Spring状态机的POM与Spring MVC没有任何依赖关系 你需要配合会话范围来作。不过,如果你是 使用网页应用时,你已经拉取了这些依赖 直接来自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());
}
在前面的例子中,行动1和行动2豆子附着在进入和退出分别是州。以下示例定义了这些动作(和行动3):
@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);
}
}
你可以直接实现行动作为匿名函数或创作
你自己的实现,并定义合适的实现为
豆。
在上述例子中,行动3使用 SpEL 表达式发送事件.E1事件变为
一台状态机。
StateContext描述在用StateContext. |
17.2. 反应性行动
正常行动接口是一种简单的函数式方法StateContext以及归来的虚空。这里没有阻挡,除非你阻挡
在方法本身中,这有点问题,因为框架不能
要清楚里面到底发生了什么。
public interface Action<S, E> {
void execute(StateContext<S, E> context);
}
为了克服这个问题,我们内部进行了调整行动处理至
处理普通Java功能夺取StateContext并返回单.这样我们可以调用行动,并且以完全反应的方式
只有在订阅且不阻塞时才执行动作
等待完成。
public interface ReactiveAction<S, E> extends Function<StateContext<S, E>, Mono<Void>> {
}
|
内部老化 |
18. 使用守卫
正如《值得铭记的事》中所示,警卫1和守卫2豆子附着在入口处,且
分别是退出州。
以下示例也使用了事件中的守卫:
@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')");
}
你可以直接实现警卫作为匿名函数或创作
你自己的实现,并定义合适的实现为
豆。在上述例子中,guardExpression检查S 是否扩展了
状态变量命名为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 表达式替代
国民警卫队全面实施。唯一的要求是表达式需要
返回布尔满足警卫实现。这可以是
用guardExpression()函数
表达作为论点。
18.2. 响应式守卫
正常警卫接口是一种简单的函数式方法StateContext以及返回的布尔值。这里没有阻挡,除非你阻挡
在方法本身中,这有点问题,因为框架不能
要清楚里面到底发生了什么。
public interface Guard<S, E> {
boolean evaluate(StateContext<S, E> context);
}
为了克服这个问题,我们内部进行了调整警卫处理至
处理普通Java功能夺取StateContext并返回单<布尔>.这样我们可以完全被动地呼叫警戒
只有在订阅且不阻塞的情况下才评估
等待返回值的完成。
public interface ReactiveGuard<S, E> extends Function<StateContext<S, E>, Mono<Boolean>> {
}
|
内部老化 |
19. 使用扩展状态
假设你需要创建一个状态机来跟踪 很多时候,用户只是按下键盘上的某个键,然后就终止了 当按键被按1000次时。一个可能但非常天真的解决方案 每按1000次键创建一个新状态。 你可能突然拥有一个天文数字数字的 而这自然并不实用。
这时扩展状态变量就派上用场了,不需要 为了增加更多状态来驱动状态机的变化。相反 你可以在过渡时简单地更改变量。
状态机有一个方法称为getExtendedState().它返回一个
接口称为扩展状态,这提供了扩展状态的访问
变量。你可以直接通过状态机访问这些变量,或者通过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");
}
};
}
如果你需要收到扩展状态变量的通知
变更时,你有两个选择:要么使用StateMachineListener或
收听extendedStateChanged(key, value)回调。以下示例
使用扩展状态已更改方法:
public class ExtendedStateVariableListener
extends StateMachineListenerAdapter<String, String> {
@Override
public void extendedStateChanged(Object key, Object value) {
// do something with changed variable
}
}
或者,你也可以为 实现 Spring Application 上下文监听器扩展状态已变.正如《监听状态机事件》中提到的,
你也可以收听所有内容StateMachineEvent事件。
以下示例使用onApplicationEvent监听州内变化:
public class ExtendedStateVariableEventListener
implements ApplicationListener<OnExtendedStateChanged> {
@Override
public void onApplicationEvent(OnExtendedStateChanged event) {
// do something with changed variable
}
}
20. 使用StateContext
StateContext是最重要的物品之一
在处理状态机时,它被传递成各种方法
以及回调以给出状态机的当前状态,
它可能走向何方。你可以把它看作是
当前状态机阶段的快照,当
当StateContext被回收。
在 Spring Statemachine 1.0.x 版本中,StateContext使用方式相对天真
比如它作为简单的“POJO”来传递信息。
从Spring Statemachine 1.1.x开始,它的作用变得非常重要
通过将其设为国家机器中的一等公民而改进。 |
你可以使用StateContext获取以下内容:
-
现状
消息或事件(或者他们的消息头,如果已知)。 -
状态机的
扩展状态. -
这
状态机本身。 -
可能的状态机错误。
-
至今
过渡,如果适用。 -
状态机的源状态。
-
状态机的目标状态。
-
现状
阶段如《阶段》所述。
StateContext传递为各种分量,例如行动和警卫.
21. 触发过渡
驱动状态机是通过使用触发的转换来实现的
被触发点控制。目前支持的触发器有事件触发器和计时触发器.
21.1. 使用事件触发器
事件触发器是最有用的触发器,因为它让你
通过向状态机发送事件直接交互。这些
事件也被称为信号。你可以在过渡中添加触发条件
在配置过程中将一个状态与之关联。
以下示例展示了如何实现:
@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();
}
无论你发送一个事件还是多个事件,结果总是序列
结果。这是因为在存在多重需求时,结果会
从那些区域的多台机器回来。这点已显示出来
采用方法发送事件收集这会给出一份结果列表。方法
本身就是一种句法上的糖分收集通量作为列表。如果有
仅一个地区,本列表包含一个结果。
Message<String> message1 = MessageBuilder
.withPayload("E1")
.build();
Mono<List<StateMachineEventResult<String, String>>> results =
stateMachine.sendEventCollect(Mono.just(message1));
results.subscribe();
| 在退回助焊剂订阅之前,什么都不会发生。更多详情请参见 StateMachineEventResult。 |
上述例子通过构造单包皮
一个消息并订阅了《Returned》通量结果。消息让
我们会在事件中添加任意额外信息,这些信息随后是可见的
自StateContext比如你实现动作时。
消息头通常传递到机器运行至
完成任务以完成特定事件。例如,如果某个事件导致
转变为状态一个这些 的 是匿名的过渡到
州B,原始事件可为州内行动或守卫提供B. |
也可以发送通量消息的代价是发送
一个带有单.
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. 使用计时触发器
计时触发器当需要触发某些东西时非常有用
自动生成,无需任何用户作。触发被加到一个
通过在配置过程中关联定时器来实现转换。
目前,支持的定时器有两种类型,一种是发射的 连续且一旦进入源态即触发。 以下示例展示了如何使用触发器:
@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
}
}
上述例子有三种状态:第一季,第二季和第三季.我们有正常
外部过渡第一季自第二季以及来自第一季自第三季跟
事件E1和E2分别。有趣的部分
为了与计时触发器当我们定义时
源态的内部转移第二季和第三季.
对于两个变迁,我们调用行动豆子 (计时器动作),其中
源状第二季使用定时器和第三季使用计时器一次.
给出的数值单位为毫秒(1000毫秒级,或两者均为一秒)。
一旦状态机收到事件E1,它会进行一个转移
从第一季自第二季计时器开始了。当该州是第二季,计时触发器运行并导致与该关系相关的转移
状态——在此情况下,是具有计时器动作定义。
一旦状态机接收到E2,事件发生转移
从第一季自第三季计时器开始了。该计时器仅执行一次
在状态输入(定时器定义的延迟之后)。
在幕后,计时器是简单的触发器,可能会引发
过渡期会发生。定义带有计时器()保持
只有当源状态处于激活状态时,触发并导致转变。
与 的过渡计时器一次()有点不同,因为它
只有在实际进入源状态时才会触发。 |
用计时器一次()如果你希望延迟后能有进展
进入状态时,恰好一次。 |
22. 监听状态机事件
有些用例你想知道发生了什么 状态机、对某事的反应,或者获取日志细节 用于调试。Spring 状态机提供添加监听器的接口。这些听众 然后在各种状态变化时提供回电选项, 诸如此类的事情发生。
你基本上有两个选择:听春季申请 上下文事件或直接将监听器附加到状态机上。两者 这些基本上提供的信息是一样的。一个产生 事件作为事件类,另一个则通过监听器产生回调 接口。这两种方式各有利弊,稍后我们会详细讨论。
22.1. 应用上下文事件
应用上下文事件类包括OnTransitionStartEvent,OnTransitionEvent,OnTransitionEndEvent,OnStateExitEvent,OnStateEntryEvent,OnStateChangedEvent,OnStateMachineStart,OnStateMachineStop以及其他扩展基础事件类的,StateMachineEvent.这些可以直接用SpringApplicationListener.
状态机通过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();
}
}
上下文事件也可以通过以下方式自动启用@EnableStateMachine,
跟状态机用于制造机器并注册为豆子,
如下示例所示:
@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) {
}
}
在前面的例子中,我们创建了自己的监听类
(StateMachineEventListener)且扩展StateMachineListenerAdapter.
这状态上下文监听者方法提供以下StateContext不同阶段的变化。你可以在相关资料中了解更多用StateContext.
一旦你定义了自己的监听者,就可以在
通过使用addStateListener方法。这是一个问题
决定是否要用Spring配置接上,还是直接接
在应用生命周期的任何时间手动作。
以下示例展示了如何附加监听器:
public class Config7 {
@Autowired
StateMachine<States, Events> stateMachine;
@Bean
public StateMachineEventListener stateMachineEventListener() {
StateMachineEventListener listener = new StateMachineEventListener();
stateMachine.addStateListener(listener);
return listener;
}
}
22.3. 局限性与问题
Spring 应用上下文并不是最快的事件总线,所以我们
建议考虑状态机的事件速率
发送。为了更好的性能,可能更适合使用StateMachineListener接口。正因如此,
你可以使用背景事件旗帜@EnableStateMachine和@EnableStateMachineFactory以禁用 Spring 应用上下文
如前文所述。
以下示例展示了如何禁用 Spring 应用上下文事件:
@Configuration
@EnableStateMachine(contextEvents = false)
public class Config8
extends EnumStateMachineConfigurerAdapter<States, Events> {
}
@Configuration
@EnableStateMachineFactory(contextEvents = false)
public class Config9
extends EnumStateMachineConfigurerAdapter<States, Events> {
}
23. 上下文整合
与状态机交互有一定限制,以下方式是 要么监听其事件,要么使用带有状态和 转换。有时候,这种方法会过于有限, verbose 用于与应用程序进行交互,状态机与之交互 工程。针对这个特定场景,我们制作了一款Spring风格 上下文集成,方便插入状态机功能 进你的豆子里。
现有注释已经过协调,以便访问 通过监听状态机事件获得的状态机执行点。
你可以使用@WithStateMachine注释用于关联状态
机器上已有豆子。然后你就可以开始加了
支持该豆方法的注释。
以下示例展示了如何实现:
@WithStateMachine
public class Bean1 {
@OnTransition
public void anyTransition() {
}
}
你也可以从以下
通过使用注释来实现应用上下文名称田。
以下示例展示了如何实现:
@WithStateMachine(name = "myMachineBeanName")
public class Bean2 {
@OnTransition
public void anyTransition() {
}
}
有时候,使用起来更方便机器编号,这算是个东西
你可以设置以更好地识别多个实例。该ID对应为
这getId()在状态机接口。
以下示例展示了如何使用它:
@WithStateMachine(id = "myMachineId")
public class Bean16 {
@OnTransition
public void anyTransition() {
}
}
当使用 StateMachineFactory 生成状态机时,使用动态的状态机会提供身份证,豆名默认为状态机无法使用@WithStateMachine (id = “某个id”)因为身份证仅在运行时已知。
在这种情况下,可以使用以下任一@WithStateMachine或@WithStateMachine(name = “stateMachine”)工厂生成的所有状态机都会绑定到你的豆子上。
你也可以使用@WithStateMachine作为元注释,如图所示
在前面的例子中。在这种情况下,你可以用和我的豆子一起.
以下示例展示了如何实现:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@WithStateMachine(name = "myMachineBeanName")
public @interface WithMyBean {
}
| 这些方法的返回类型无关紧要,实际上是 丢弃。 |
23.1. 实现集成
你可以启用所有功能@WithStateMachine通过
这@EnableWithStateMachine注释,导入所需的
配置到Spring应用上下文中。双@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() {
}
}
如果机器不是以豆子形式创建的,你需要设置豆子工厂对于一台机器,如前述示例所示。否则,tge machine 是
不知道有操作员会呼叫你的@WithStateMachine方法。 |
23.2. 方法参数
每个注释都支持完全相同的可能方法集
但运行时行为会因
注释本身以及注释方法被调用的阶段。自
更好地理解上下文的运作方式,参见用StateContext.
| 关于方法参数之间的差异,请参见对 单独注释,见本文档后面。 |
实际上,所有带注释的方法都通过使用 Spring SPEL 调用
表达式,这些表达式是在过程中动态构建的。要做
这项工作中,这些表达式需要有一个根对象(用于取值)。
这个根对象是StateContext.我们也做了一些
内部调整,使得访问变得可能StateContext方法
直接使用,无需经过上下文账号。
最简单的方法参数是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被使用。在这个注释中,属性的
你可以使用源和目标用来确认过渡。如果源和目标保持空,任何转换都匹配。
以下示例展示了如何使用@OnTransition注解
(记住@OnTransitionStart和@OnTransitionEnd工作原理相同):
@WithStateMachine
public class Bean5 {
@OnTransition(source = "S1", target = "S2")
public void fromS1ToS2() {
}
@OnTransition
public void anyTransition() {
}
}
默认情况下,你不能使用@OnTransition带有状态和的注释
你创建的事件枚举,是因为Java语言的限制。
因此,你需要使用字符串表示。
此外,你还可以访问事件头和扩展状态通过向方法添加所需的参数。方法
然后 自动调用这些参数。
以下示例展示了如何实现:
@WithStateMachine
public class Bean6 {
@StatesOnTransition(source = States.S1, target = States.S2)
public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
}
}
不过,如果你想要类型安全的注释,可以
创建新的注释并使用@OnTransition作为一种元注释。
该用户级注释可以引用实际状态和
事件枚举,框架试图以同样的方式匹配这些事件。
以下示例展示了如何实现:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {
States[] source() default {};
States[] target() default {};
}
在前面的例子中,我们创建了@StatesOnTransition定义 的注释源和目标以类型安全的方式。
以下示例使用了豆子中的该注释:
@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() {
}
}
就像你用过渡注释一样,你可以定义 目标和源状态。以下示例展示了如何实现:
@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() {
}
}
状态进出的方法表现相同,如下示例所示:
@WithStateMachine
public class Bean11 {
@OnStateEntry
public void anyStateEntry() {
}
@OnStateExit
public void anyStateExit() {
}
}
23.5. 事件注释
有一个事件相关的注释。它被命名为@OnEventNotAccepted.
如果你指定事件财产,你可以监听某个特定事件,而不是
接受。如果你没有指定事件,也可以列出任何不属于事件的事件
接受。以下示例展示了两种使用@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() {
}
}
如果状态机出现异常错误,@OnStateMachineStop注释被调用。以下示例展示了如何使用它:
@WithStateMachine
public class Bean14 {
@OnStateMachineError
public void onStateMachineError() {
}
}
24. 使用StateMachineAccessor
状态机是与状态机通信的主要接口。
有时候,你可能需要变得更有活力,并且
对状态机内部结构的程序访问及其
嵌套的机器和区域。对于这些用例,状态机暴露一个功能接口,称为StateMachineAccessor,该 表示
一个用于访问个人的接口状态机和地区实例。
状态机函数是一个简单的函数式接口,使得
你应用StateMachineAccess与状态机的接口。跟
JDK 7,这些代码会生成一些冗长的代码。然而,使用 JDK 8 lambda 时,
该文件相对不多词。
这所有地区方法可访问所有地区实例
一台状态机。以下示例展示了如何使用它:
stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.setRelay(stateMachine));
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access -> access.setRelay(stateMachine));
这doWithRegion方法提供单一访问地区实例
状态机。以下示例展示了如何使用它:
stateMachine.getStateMachineAccessor().doWithRegion(function -> function.setRelay(stateMachine));
stateMachine.getStateMachineAccessor()
.doWithRegion(access -> access.setRelay(stateMachine));
这withAllRegions该方法可访问所有地区实例
一台状态机。以下示例展示了如何使用它:
for (StateMachineAccess<String, String> access : stateMachine.getStateMachineAccessor().withAllRegions()) {
access.setRelay(stateMachine);
}
stateMachine.getStateMachineAccessor().withAllRegions()
.stream().forEach(access -> access.setRelay(stateMachine));
这withRegion方法提供单一访问地区实例
状态机。以下示例展示了如何使用它:
stateMachine.getStateMachineAccessor()
.withRegion().setRelay(stateMachine);
25. 使用状态机拦截者
而不是使用StateMachineListener接口,你可以
使用一个状态机拦截者.一个概念上的区别是你可以使用
拦截器拦截并停止当前状态
改变或改变其过渡逻辑。与其实现完整的接口,
你可以使用一个叫做适配器类的状态机拦截器适配器以覆盖
默认的无作方法。
你可以通过以下方式注册拦截机StateMachineAccessor.概念
截击器是一个相对深的内部特征,因此不是
直接通过状态机接口。
以下示例展示了如何添加状态机拦截者并选择覆盖
方法:
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的参考文档 (可在此处获取)。 |
安全防护的第一层次自然是保护事件, 这才真正驱动着未来 发生在状态机中。然后你可以定义更细致的安全设置 用于过渡和动作。这类似于让员工进入架构物 然后还能进入架构内的特定房间,甚至提供那个能力 在特定房间里开关灯。如果你信任 你的用户,活动安全可能就是你所需要的全部。如果没有, 你需要施加更详细的安保措施。
你可以在《安全理解》中找到更详细的信息。
| 完整示例请参见安全性示例。 |
26.1. 配置安全
所有通用的安全配置均在安全配置器,由 得到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);
}
}
如果真的需要,也可以自定义AccessDecisionManager对于两个事件和
转换。如果你不定义决策经理或
设置为零默认管理器由内部创建。
26.2. 赛事安全
事件安全在全局层面由安全配置器.
以下示例展示了如何启用事件安全:
@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);
}
}
在前面的配置示例中,我们使用一个表达式true,且总是计算
自true.使用一个总是取值为true在实际应用中这不合理,但说明了这一点
表达式需要返回true或false.我们还定义了
属性ROLE_ANONYMOUS以及一个比较类型之任何.关于属性的使用了解更多
以及表达式,参见使用安全属性和表达式。
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. 确保行动
对于状态下的行为没有专门的安全定义
但你可以通过全局方法安全来保护作
来自春季安保。这需要一个行动是
定义为代理@Bean以及其执行方法注释为@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);
}
}
详情请参见春季安全参考指南(此处可查阅)。
26.5. 使用安全属性和表达式
通常,你可以用两种方式定义安全属性:通过 通过使用安全属性和安全表达式。 属性使用起来更简单,但相对有限 功能性。表达式提供了更多功能,但也略有点 更难使用。
26.5.1. 通用属性使用
默认情况下,AccessDecisionManager事件和的实例
两者都使用一个角色投票者,意味着你可以使用角色属性
来自春季安保。
对于属性,我们有三种不同的比较类型:任何,都和大多数.这些比较类型映射到默认访问决策管理器
(正方,一致基于和共识基础分别是 。
如果你已经定义了一个自定义AccessDecisionManager,比较类型为
实际上被丢弃,因为它仅用于创建默认管理器。
26.5.2. 通用表达式使用
安全表达式必须返回以下true或false.
表达式根对象的基类为安全表达根.它提供了一些常见表达,包括
在过渡和安全两种情况下均可使用。下表
描述最常用的内置表达式:
| 表达 | 描述 |
|---|---|
|
返回 |
|
返回 |
|
返回 |
|
返回 |
|
允许直接访问代表 当前用户。 |
|
允许直接接入电流 |
|
总是对 |
|
总是对 |
|
返回 |
|
返回 |
|
返回 |
|
返回 |
|
返回 |
|
返回 |
26.6. 理解安全
本节提供了关于安全机制在 状态机。你可能不需要知道,但确实如此 透明总比隐藏所有魔法好。什么? 幕后发生的事。
安全性只有在 Spring State machine 运行在有墙环境时才有意义
用户无法直接访问该应用程序,因此
修改Spring安全安全上下文请稍作停留。
如果用户控制JVM,那么实际上就没有安全保障
完全。 |
安全的集成点是通过状态机拦截者,然后自动加入
如果启用了安全,状态机。具体类为状态机安全拦截器,截距事件和
转换。这架拦截机随后会咨询Spring安全公司的AccessDecisionManager以确定事件是否可以发送,或转移是否可以
执行。实际上,如果有决定或投票,就会发生AccessDecisionManager如果发生例外,则该事件或转换被拒绝。
因为AccessDecisionManager来自春季安保公司,我们
每个安全对象需要一个实例。这就是原因之一
是不同的活动和过渡经理。在这里,事件
而转移则是我们保护的不同类对象。
默认情况下,对于事件,投票者(事件表达投票者,EventVoter和角色投票者)被添加到AccessDecisionManager.
默认情况下,对于过渡,选民(TransitionExpressionVoter,过渡选民和角色投票者)被添加到AccessDecisionManager.
27. 状态机错误处理
如果状态机在状态转移过程中检测到内部错误 逻辑上,它可能会抛出例外。在处理该异常之前 内部,你有机会拦截。
通常,你可以使用状态机拦截者以拦截错误和
以下列表展示了其一个示例:
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;
}
})
);
}
当检测到错误时,会执行正常的事件通知机制。
这让你可以使用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
}
}
| 为转移定义的动作也有自己的错误处理 逻辑。参见过渡动作错误处理。 |
使用响应式 API 时,可能会出现动作执行错误
返回 StateMachineEventResult。有简单的机器
动作中转换到状态的错误第一季.
@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. 持久化状态机
传统上,状态机实例在 运行程序。你可以通过以下方式实现更动态的行为 动态构建器和工厂,这支持状态机 按需实现。构建状态机实例是 相对繁重的作。因此,如果你需要(例如)处理 通过使用状态机在数据库中任意改变状态,你需要 找到更好更快的方法来实现。
持久化功能允许你保存状态机的状态 进入外部存储库,随后基于 序列化状态。例如,如果你有一个数据库表管理 命令,用一个状态更新订单状态会成本过高 机器是否需要为每次更改构建一个新的实例。 持久功能允许你在没有 实例化一个新的状态机实例。
虽然你可以通过使用StateMachineListener它有一个概念上的问题。当听众
通知州变更,州变已经发生。如果
监听器内的自定义持久方法无法更新序列化
外部仓库中的状态,状态机中的状态,以及
外部存储库则处于不一致状态。
你可以用状态机拦截器来尝试拯救
在 状态
状态机内部的变化。如果拦截器回调失败,
你可以停止状态变更尝试,而不是以
不一致状态,你可以手动处理这个错误。看用状态机拦截者关于如何使用拦截机。
29.1. 使用状态机上下文
你无法持续存在状态机通过使用普通 Java
序列化,因为对象图过于丰富且包含过多
依赖其他 Spring 上下文类。状态机上下文是运行时表示状态机的一种,你可以用它来实现
将现有机器恢复为由特定状态机上下文对象。
状态机上下文包含两种不同的信息包含方式
针对儿童背景。这些通常用于机器
正交区域。首先,上下文可以包含子上下文列表
如果存在,也可以直接使用。其次,你可以
包含原始上下文子节点时使用的引用列表
但都没有到位。这些儿童引用实际上是唯一的途径
持久化一台同时运行多个并行区域的机器
独立地。
| 数据多持久示例显示 如何持久化平行区域。 |
29.2. 使用StateMachinePersister
架构状态机上下文然后恢复状态机
从那里来,如果做的话,总会有点“黑魔法”的成分
手动下载。这StateMachinePersister界面旨在简化这些问题
通过提供坚持和恢复方法。默认
该接口的实现为DefaultStateMachinePersister.
我们可以展示如何使用StateMachinePersister通过遵循
测试片段。我们先创建两个相似的配置
(机器1和机器2)注意我们可以做不同的设计
为演示提供其他方式的机器,但不是这样
这个案子很有效。以下示例配置了这两个状态机:
@Configuration
@EnableStateMachine(name = "machine1")
static class Config1 extends Config {
}
@Configuration
@EnableStateMachine(name = "machine2")
static class Config2 extends Config {
}
static class Config extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S1")
.state("S2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1");
}
}
因为我们正在使用StateMachinePersist对象,我们可以创建一个内存中的
实现。
| 此内存样本仅供演示用途。真的 应用方面,你应该使用真正的持久存储实现。 |
以下列表展示了如何使用内存采样:
static class InMemoryStateMachinePersist implements StateMachinePersist<String, String, String> {
private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();
@Override
public void write(StateMachineContext<String, String> context, String contextObj) throws Exception {
contexts.put(contextObj, context);
}
@Override
public StateMachineContext<String, String> read(String contextObj) throws Exception {
return contexts.get(contextObj);
}
}
在实例化两台不同机器后,我们可以进行传输机器1进入国家第二季通过事件E1.然后我们可以持续保存并恢复机器2.以下示例展示了如何实现:
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(Redis
RepositoryStateMachinePersist(实现了StateMachinePersist)支持将状态机持久化到Redis中。
具体实现是RedisStateMachineContextRepository,其使用方式克里奥序列化为
坚持 a状态机上下文到Redis.
为StateMachinePersister,我们有Redis相关RedisStateMachinePersister实现,该实例为
一个StateMachinePersist及其用途字符串作为其上下文宾语。
| 详细使用方法请参见事件服务示例。 |
RedisStateMachineContextRepository需要一个RedisConnection工厂让它能正常工作。我们建议使用JedisConnectionFactory(绝地联结工厂)正如前面的例子所示,是对它。
29.4. 使用StateMachineRuntimePersister
StateMachineRuntimePersister是 的简单扩展StateMachinePersist这增加了一个接口层次的方法来获得状态机拦截者与之相关。该拦截器为
在状态变化时需要在不需
停机和启动机器。
目前,已有针对该接口的实现
支持春季数据仓库。这些实现包括JpaPersistingStateMachineInterceptor,MongoDbPersistingStateMachineInterceptor, 和RedisPersistingStateMachineInterceptor.
| 详细使用方法请参见数据持久化示例。 |
30. Spring靴支撑
自动配置模块(Spring-statemachine-autoconfigure) 包含
与 Spring Boot 集成的逻辑,Spring Boot 提供了以下功能
自动配置和执行器。你只需要拥有这个Spring Statemachine。
库作为启动应用的一部分。
30.1. 监测与追踪
BootStateMachineMonitor自动创建并关联于
一台状态机。BootStateMachineMonitor是一种习俗StateMachineMonitor实现与 Spring Boot 集成MeterRegistry以及端点
通过一种习俗StateMachineTraceRepository.你也可以选择关闭这个自动配置
通过设置spring.statemachine.monitor.enabled关键false.监控示例展示了如何使用这种自动配置。
30.2. 仓库配置
如果从类路径中找到所需的类,则 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();
}
}
| 详细使用情况请参见监测示例。 |
32. 使用分布式状态
分布式状态可能是 Spring状态机。分布式状态到底是什么?一个国家 在单一状态机内,理解起来自然非常简单, 但当需要引入共享分布式状态时 通过状态机,情况会变得有些复杂。
| 分布式状态功能仍是预览功能,并非 但在这个特定版本中被认为是稳定的。我们期待如此 该功能逐渐成熟,准备迎来首个正式发布。 |
分布式状态机通过分布式状态机封装实际实例的类
一状态机.分布式状态机拦截
与状态机实例并与
分布式状态抽象通过StateMachineEnsemble接口。具体执行方式不同,
你也可以使用StateMachinePersist用于序列化的接口状态机上下文,其中包含足够的信息来重置状态机.
分布式状态机通过抽象实现, 目前仅有一个实现。它基于《动物园守护者》。
以下示例展示了如何配置基于Zookeeper的分布式状态 机器“:
@Configuration
@EnableStateMachine
public class Config
extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withDistributed()
.ensemble(stateMachineEnsemble())
.and()
.withConfiguration()
.autoStartup(true);
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
// config states
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
// config transitions
}
@Bean
public StateMachineEnsemble<String, String> stateMachineEnsemble()
throws Exception {
return new ZookeeperStateMachineEnsemble<String, String>(curatorClient(), "/zkpath");
}
@Bean
public CuratorFramework curatorClient()
throws Exception {
CuratorFramework client = CuratorFrameworkFactory
.builder()
.defaultData(new byte[0])
.connectString("localhost:2181").build();
client.start();
return client;
}
}
你可以找到基于Zookeeker的分布式当前技术文档 附录中的状态机。
32.1. 使用ZookeeperStateMachineEnsemble
ZookeeperStateMachineEnsemble本身需要两个强制设置,
一个实例策展人客户以及一个basePath.客户是策展框架路径是树的根Zookeeper 实例。
你可以选择设置cleanState,默认为true如果集合中没有成员,则清除现有数据。你可以设置
它false如果你想在内部保持分布式状态
应用程序会重启。
可选地,你可以设置 a 的大小logSize(默认
自32)以保持状态变更的历史。这份工作的重要性
设置必须是二的幂。32通常是个不错的默认选项
价值。如果某个特定状态机被超过的
日志大小时,会进入错误状态并与
这表明它已经失去了历史和完全重建
同步状态。
33. 测试支持
我们还增加了一套效用类,以便于状态测试 机器实例。这些工具在框架本身中使用,但也包括 对终端用户非常有用。
StateMachineTestPlanBuilder建造 AStateMachineTestPlan,
该方法有一个(称为测试()).那个方法有计划。StateMachineTestPlanBuilder包含一个流利构建器API,可以让你添加内容
计划的步骤。在这些步骤中,你可以发送事件并进行检查
各种条件,如状态变化、转变和扩展状态
变量。
以下示例使用国家机器构建器构建状态机:
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();
}
在接下来的测试计划中,我们有两个步骤。首先,我们检查首字母
状态(四)确实是设定的。其次,我们发送一个事件(E1)并期待
发生一次状态变化,机器最终会处于第一季.
以下列表展示了测试计划:
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 导入方式:
所有可能的预期结果选项都已在 Javadoc 中记录StateMachineTestPlanStepBuilder. |
34. Eclipse 建模支持
支持定义带有UI建模的状态机配置 通过Eclipse Papyrus框架。
通过Eclipse向导,你可以用UML图创建一个新的Papyrus模型
语言。在这个例子中,它被命名为简单机.然后你
你可以从各种图纸中选择,你必须选择状态机
图.
我们想创建一个具有两个状态的机器(第一季和第二季),其中第一季是初始状态。然后,我们需要创造事件E1进行过渡
从第一季自第二季.在Papyrus中,机器看起来就像某种东西
以下示例:
在幕后,原始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的现有模型时,你有三个
文件:。地,。表示法和.uml.如果模型没有在你的环境中创建
Eclipse的会话,它不懂得如何打开实际状态
图表。这是 Papyrus 插件中已知的问题,有一个简单的方法
变通办法。在Papyrus的视角下,你可以看到一个模型浏览器
你的模型。双击状态机图,其中
指示Eclipse用它正确的Papyrus打开这个特定模型
建模插件。 |
34.1. 使用UmlStateMachineModelFactory
当你的项目中放置了UML文件后,你可以将其导入到你的
配置方式为StateMachineModelConfigurer哪里StateMachineModelFactory与一个模型相关联。UmlStateMachineModelFactory是一家懂得如何
处理 Eclipse 的 UML 结构Papyrus_generated。源UML文件可以
要么被赋予一个泉水资源或者作为普通位置字符串。
以下示例展示了如何创建 的实例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 与保护器 和 动作,定义为豆子。这些需要连接到UML 通过其内部建模结构。以下章节展示了 如何在UML定义中定义自定义豆子引用。 注意,也可以手动注册特定方法 但不把这些定义为豆子。
如果UmlStateMachineModelFactory被创造为一颗豆子,其ResourceLoader自动接线以查找已注册的动作,
警卫。你也可以手动定义一个StateMachineComponentResolver,然后用来求得这些
组件。工厂里还有 registerAction 和 registerGuard 方法,你可以用它们来注册这些组件。更多内容
关于这个,参见用StateMachineComponentResolver.
UML模型在实现方面相对较为宽松,如 Spring状态机本身。Spring Statemachine 留下了许多实现功能的方法, 功能直到实际实现。以下部分如下 通过 Spring Statemachine 如何基于 Eclipse Papyrus 插件。
34.1.1. 使用StateMachineComponentResolver
下一个例子展示了UmlStateMachineModelFactory定义为
一个StateMachineComponentResolver,该记录为我的行动和我的守卫分别是功能。注意这些分量
不是被创造成豆子。以下列表展示了该示例:
@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. 创建模型
我们首先创建一个空状态机模型,如下图所示:
你可以先创建一个新模型并命名,如下图所示:
然后你需要选择状态机图,具体如下:
最终你得到的是一个空的状态机。
在前面的图片中,你应该创建了一个名为型.
你本该得到三个文件:model.di,model.符号和model.uml.然后你可以在其他任何文件中使用这些文件
Eclipse实例。此外,你还可以导入model.uml变成了
Spring状态机。
34.3. 定义州
状态标识符来自图中的组件名称。 你的机器必须有一个初始状态,可以通过添加来实现 先是根元素,然后画出一个过渡到你自己的初始状态, 如下图所示:
在前图中,我们添加了根元素和初始状态(第一季).然后我们画了一个过渡
在这两者之间以表示第一季是一个初始态。
在前一张图片中,我们添加了第二个状态(第二季)并增加了
S1和S2(表明我们有两个状态)。
34.4. 决定性事件
要将事件与过渡关联,你需要创建一个信号
(E1,在此中)。要做到这一点,请选择 RootElement → 新子信号→信号。
下图展示了结果:
然后你需要用新的信号箱子来创建一个SignalEvent,E1.
要这样做,选择 RootElement → 新子节点 → SignalEvent。
下图展示了结果:
现在你已经定义了信号事件你可以用它来关联
一个带有转折的触发点。关于这方面的更多信息,请参见“定义过渡”。
34.5. 定义过渡
你可以通过在
源态和目标状态。在前面的图像中,我们有状态第一季和第二季以及
两者之间的匿名过渡。我们想要关联事件E1有了那个转变。我们选择一个过渡,创造一个新的
触发,并为其定义SignalEventE1,如下图所示:
这会得到类似下图所示的排列:
| 如果你省略了 SignalTEvent 作为过渡,它就会变成 匿名过渡。 |
34.6. 定义计时器
过渡也可以基于时间事件发生。Spring状态机 支持两种类型的定时器,一种持续在 背景和当状态为 时延迟发射一次的 进入。
要向模型资源管理器添加新的TimeEvent子,修改“When ”作为 表达式定义为 LiteralInteger。它的数值(以毫秒计)变成了计时器。 让计时器持续触发的“离去”是相对虚假的。
为了定义一个在进入状态时触发的基于时间的事件,过程恰好为 与之前描述相同,但将 Is Relative 设置为 true。下图 显示结果:
然后用户可以选择这些定时事件之一,而不是针对特定转换的信号事件。
34.7. 定义一个选择
选择的定义是通过将一个输入的转移画入CHOICE 状态,并从该状态画出多个输出转换到目标的 国家。 我们 的配置模型StateConfigurer你可以定义if/elseif/else 结构。然而,使用 UML 时,我们需要处理用于输出过渡的单个守卫。
你必须确保为转移定义的守卫不重叠,这样,无论发生什么,在任何给定情况下,只有一个守卫被评估为真 时间。 这为选择分支提供了精确且可预测的结果 评估。 此外,我们建议保留一个过渡不加保护以确保至少有一条过渡路径。下图展示了在三个分支中做出选择的结果:
| Junction 的工作原理类似,只是允许多个进站 转换。 因此,它与选择的行为是纯粹的 学术。 选择出境过渡的实际逻辑完全相同。 |
34.8. 定义交汇点
参见定义一个选择。
34.9. 定义进出点
你可以使用EntryPoint和ExitPoint来创建受控的进出状态带有子状态。在下面的状态图中,事件E1和E2通过进入和退出状态表现出正常态行为第二季,其中正常态行为通过进入初始状态发生S21.
使用事件E3将机器引入进入EntryPoint,随后通向第二季但不激活初始状态S21随时。同样地退出带事件的出口点E4控制具体的出口进入状态第四季,而正常的退出行为则是第二季将机器进入状态第三季. 在州期间第二季,你可以从以下选择 事件E4和E2将机器引入状态第三季或第四季, 分别。 下图展示了结果:
| 如果状态被定义为子机引用,并且你需要使用入口点和出口点,你必须外部定义一个 ConnectionPointReference,其中其入口和出口引用设置为指向正确的入口或出口点在子机引用内。只有在那之后,才能锁定一个正确从外部链接到内部的过渡一个子机引用。使用 ConnectionPointReference,你可能需要从属性中查找这些设置,高级→ UML → →入口/出口。UML 规范允许你定义多个条目和出口。 然而 而状态机则只允许一个。 |
34.11. 定义分叉与连接
在Papyrus中,叉和连接都以条形表示。如图所示在下一张图中,你需要从中绘制一个出场过渡叉进入国家第二季拥有正交区域。加入则是反过来,其中
合并态是从进入的转移中收集的。
34.12. 定义行动
你可以通过使用行为来关联 SWTATE 的进入和退出动作。 关于此事的更多信息,请参见《定义一个豆子的参考》。
34.12.1. 使用初始动作
定义了一个初始动作(如配置动作中所示) 通过在转移中添加一个从初始状态引出的动作,在UML中实现 标记进入实际状态。当状态 机器启动了。
34.13. 定义守卫
你可以先添加一个约束,然后定义 其规范为 OpaqueExpression,工作原理相同 作为定义Beans的引用。
34.14. 定义一个豆子的引用
当你需要在任何UML效果中做豆子引用时,
动作,或者说守卫,你可以用功能行为或不透明度行为,其中定义的语言需要
是豆而语言 body msut 有一个 bean reference ID。
34.15. 定义 SpEL 引用
当你需要用 SpEL 表达式代替 bean 引用时
任何UML效果、动作或守卫,你都可以通过以下方式实现功能行为或不透明度行为,其中定义的语言需要
是SPEL语言主体必须是 SpEL 表达式。
34.16. 使用子机引用
通常,使用子状态时,会把子状态抽入状态 图表本身。图表可能会变得过于复杂和庞大,无法 跟随,因此我们也支持将子状态定义为状态机 参考。
要创建子机器引用,首先必须创建一个新的图表并给它命名 (例如,子状态机图)。下图展示了可选菜单:
给新图纸你所需的设计。 下图展示了一个简单的设计示例:
从你想链接的州(这里是我州)第二季),点击潜艇选入并选择你的关联机器(在我们的例子中,子状态机).
最后,在下图中,你可以看到那个状态第二季与子状态机作为
次级国家。
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 Statemachine中的仓库。
35.1. 仓库配置
你可以把机器配置放在外部 存储,可以按需加载,而不是创建静态存储 通过使用 Java 配置或基于 UML 的配置进行配置。这 集成通过 Spring Data Repository 抽象实现。
我们创造了特别节目StateMachineModelFactory实现
叫RepositoryStateMachineModelFactory.它可以使用底座
仓库接口(StateRepository,过渡仓库,行动仓库和GuardRepository)以及基底实体
接口(存储状态,存储库转换,RepositoryAction和仓库守卫).
由于 Spring Data 中实体和仓库的工作方式,
从用户角度来看,读取权限可以完全抽象化
在RepositoryStateMachineModelFactory.没必要
知道仓库实际使用的映射实体类。
写入仓库总是依赖于使用真实的
仓库专用实体类。从机器配置角度看
从某种角度看,我们不需要知道这些,也就是说我们不需要知道
无论是实际实现是JPA、Redis还是其他
而Spring Data支持。使用实际的仓库相关工具
当你手动尝试写新时,entity 类就会出现
状态或转换到有后备的仓库。
实体类存储状态和存储库转换有machineId场,你可以随时使用,并可用于区分不同的配置——例如,如果机器被建造出来 通过状态机工厂. |
实际实现将在后续章节中详细说明。以下图片是UML对应的仓库状态图 配置。
35.1.1. JPA
JPA的实际仓库实现有Jpa州仓库,JpaTransitionRepository,Jpa行动仓库, 和JpaGuardRepository,这些 都由实体类支持JpaRepositoryState,JpaRepository 过渡,JpaRepositoryAction(JpaRepositoryAction)和JpaRepositoryGuard分别。
不幸的是,版本“1.2.8”不得不对JPA的实体模型进行更改,涉及使用的表名。此前,生成的表名总是带有前缀JPA_REPOSITORY_,由实体类推导出 名字。 由于这导致数据库对数据库对象长度的限制,所有实体类都有强制表名的特定定义。 例如JPA_REPOSITORY_STATE现在是“STATE”——以此类推,其他ntity 类。 |
手动更新JPA状态和转移的通用方法如下:如下示例(相当于SimpleMachine中展示的机器):
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
以下示例也等价于SimpleSubMachine中展示的机器。
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
void addConfig() {
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
JpaRepositoryState stateS3 = new JpaRepositoryState("S3");
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS22 = new JpaRepositoryState("S22");
stateS22.setParentState(stateS2);
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
stateRepository.save(stateS21);
stateRepository.save(stateS22);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS21, stateS22, "E2");
JpaRepositoryTransition transitionS21ToS22 = new JpaRepositoryTransition(stateS2, stateS3, "E3");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
transitionRepository.save(transitionS21ToS22);
}
首先,您必须访问所有仓库。以下示例展示了如何作:
@Autowired
StateRepository<JpaRepositoryState> stateRepository;
@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;
@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;
@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;
其次,你必须创建动作和防御。以下示例展示了如何实现:
JpaRepositoryGuard foo0Guard = new JpaRepositoryGuard();
foo0Guard.setName("foo0Guard");
JpaRepositoryGuard foo1Guard = new JpaRepositoryGuard();
foo1Guard.setName("foo1Guard");
JpaRepositoryAction fooAction = new JpaRepositoryAction();
fooAction.setName("fooAction");
guardRepository.save(foo0Guard);
guardRepository.save(foo1Guard);
actionRepository.save(fooAction);
第三,你必须创建状态。以下示例展示了如何实现:
JpaRepositoryState stateS0 = new JpaRepositoryState("S0", true);
stateS0.setInitialAction(fooAction);
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
stateS1.setParentState(stateS0);
JpaRepositoryState stateS11 = new JpaRepositoryState("S11", true);
stateS11.setParentState(stateS1);
JpaRepositoryState stateS12 = new JpaRepositoryState("S12");
stateS12.setParentState(stateS1);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
stateS2.setParentState(stateS0);
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS211 = new JpaRepositoryState("S211", true);
stateS211.setParentState(stateS21);
JpaRepositoryState stateS212 = new JpaRepositoryState("S212");
stateS212.setParentState(stateS21);
stateRepository.save(stateS0);
stateRepository.save(stateS1);
stateRepository.save(stateS11);
stateRepository.save(stateS12);
stateRepository.save(stateS2);
stateRepository.save(stateS21);
stateRepository.save(stateS211);
stateRepository.save(stateS212);
第四,也是最后,你必须创建过渡。以下示例展示了如何实现:
JpaRepositoryTransition transitionS1ToS1 = new JpaRepositoryTransition(stateS1, stateS1, "A");
transitionS1ToS1.setGuard(foo1Guard);
JpaRepositoryTransition transitionS1ToS11 = new JpaRepositoryTransition(stateS1, stateS11, "B");
JpaRepositoryTransition transitionS21ToS211 = new JpaRepositoryTransition(stateS21, stateS211, "B");
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "C");
JpaRepositoryTransition transitionS1ToS0 = new JpaRepositoryTransition(stateS1, stateS0, "D");
JpaRepositoryTransition transitionS211ToS21 = new JpaRepositoryTransition(stateS211, stateS21, "D");
JpaRepositoryTransition transitionS0ToS211 = new JpaRepositoryTransition(stateS0, stateS211, "E");
JpaRepositoryTransition transitionS1ToS211 = new JpaRepositoryTransition(stateS1, stateS211, "F");
JpaRepositoryTransition transitionS2ToS21 = new JpaRepositoryTransition(stateS2, stateS21, "F");
JpaRepositoryTransition transitionS11ToS211 = new JpaRepositoryTransition(stateS11, stateS211, "G");
JpaRepositoryTransition transitionS0 = new JpaRepositoryTransition(stateS0, stateS0, "H");
transitionS0.setKind(TransitionKind.INTERNAL);
transitionS0.setGuard(foo0Guard);
transitionS0.setActions(new HashSet<>(Arrays.asList(fooAction)));
JpaRepositoryTransition transitionS1 = new JpaRepositoryTransition(stateS1, stateS1, "H");
transitionS1.setKind(TransitionKind.INTERNAL);
JpaRepositoryTransition transitionS2 = new JpaRepositoryTransition(stateS2, stateS2, "H");
transitionS2.setKind(TransitionKind.INTERNAL);
transitionS2.setGuard(foo1Guard);
transitionS2.setActions(new HashSet<>(Arrays.asList(fooAction)));
JpaRepositoryTransition transitionS11ToS12 = new JpaRepositoryTransition(stateS11, stateS12, "I");
JpaRepositoryTransition transitionS12ToS212 = new JpaRepositoryTransition(stateS12, stateS212, "I");
JpaRepositoryTransition transitionS211ToS12 = new JpaRepositoryTransition(stateS211, stateS12, "I");
JpaRepositoryTransition transitionS11 = new JpaRepositoryTransition(stateS11, stateS11, "J");
JpaRepositoryTransition transitionS2ToS1 = new JpaRepositoryTransition(stateS2, stateS1, "K");
transitionRepository.save(transitionS1ToS1);
transitionRepository.save(transitionS1ToS11);
transitionRepository.save(transitionS21ToS211);
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS1ToS0);
transitionRepository.save(transitionS211ToS21);
transitionRepository.save(transitionS0ToS211);
transitionRepository.save(transitionS1ToS211);
transitionRepository.save(transitionS2ToS21);
transitionRepository.save(transitionS11ToS211);
transitionRepository.save(transitionS0);
transitionRepository.save(transitionS1);
transitionRepository.save(transitionS2);
transitionRepository.save(transitionS11ToS12);
transitionRepository.save(transitionS12ToS212);
transitionRepository.save(transitionS211ToS12);
transitionRepository.save(transitionS11);
transitionRepository.save(transitionS2ToS1);
你可以在这里找到完整的示例。这个示例还展示了如何从已有的 JSON 文件预填充一个仓库,该文件包含实体类的定义。
35.1.2. Redis
Redis实例的实际仓库实现有RedisStateRepository,RedisTransitionRepository,RedisActionRepository, 和RedisGuard仓库,这些 都由实体类支持RedisRepositoryState,RedisRepository 过渡,RedisRepositoryAction(重现仓库行动)和RedisRepositoryGuard分别。
下一个例子展示了手动更新Redis状态和转换的通用方法。这相当于SimpleMachine中展示的机器。
@Autowired
StateRepository<RedisRepositoryState> stateRepository;
@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;
void addConfig() {
RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
RedisRepositoryState stateS3 = new RedisRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
以下示例等价于SimpleSubMachine中展示的机器:
@Autowired
StateRepository<RedisRepositoryState> stateRepository;
@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;
void addConfig() {
RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
RedisRepositoryState stateS3 = new RedisRepositoryState("S3");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
stateRepository.save(stateS3);
RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS2ToS3);
}
35.1.3. MongoDB
MongoDB实例的实际仓库实现有MongoDb状态仓库,MongoDbTransitionRepository,MongoDb行动仓库, 和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. 仓库持久化
除了存储机器配置(如仓库配置所示),在外部仓库中,你还可以将机器持久化到仓库中。
这StateMachineRepository接口是一个中央接入点,其与机器持久互,并由实体类支持。RepositoryStateMachine.
35.2.1. JPA
JPA 的实际仓库实现是JpaStateMachineRepository,该 以实体类为支持JpaRepositoryStateMachine.
以下示例展示了为JPA持久化机器的通用方法:
@Autowired
StateMachineRepository<JpaRepositoryStateMachine> stateMachineRepository;
void persist() {
JpaRepositoryStateMachine machine = new JpaRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}
35.2.2. Redis
Redis的实际仓库实现是RedisStateMachineRepository,该 以实体类为支持RedisRepositoryStateMachine.
以下示例展示了Redis机器持久化的通用方法:
@Autowired
StateMachineRepository<RedisRepositoryStateMachine> stateMachineRepository;
void persist() {
RedisRepositoryStateMachine machine = new RedisRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}
35.2.3. MongoDB
MongoDB 的实际仓库实现是MongoDbStateMachineRepository,该 以实体类为支持MongoDbRepositoryStateMachine.
以下示例展示了MongoDB中持久化机器的通用方法:
@Autowired
StateMachineRepository<MongoDbRepositoryStateMachine> stateMachineRepository;
void persist() {
MongoDbRepositoryStateMachine machine = new MongoDbRepositoryStateMachine();
machine.setMachineId("machine");
machine.setState("S1");
// raw byte[] representation of a context
machine.setStateMachineContext(new byte[] { 0 });
stateMachineRepository.save(machine);
}