SpringStateMachine学习

配置状态机

状态

  • 分层状态

  • withStates() 配置状态 states状态列表

可以使用多个withStates进行parent分层

  • 配置区域:当相同的分层状态机具有多组状态时,每个都具有初始状态,就产生正交状态,多个独立区域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.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);
  • 配置转换:三种不同类型的转换external, internal和local
1
2
3
4
5
6
7
8
9
10
11
.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);
  • END 标记为结束状态 .end(States.SF)

只需使用end()方法将特定状态标记为结束状态。这可以在每个单独的子机器或区域最多进行一次

  • ?history 历史状态 .history(States3.SH, History.SHALLOW);

可以为每个单独的状态机定义历史状态一次。你需要选择其状态标识,History.SHALLOW或 History.DEEP分别

  • withChoice() 根据警卫结果选择状态

  • ?withJunction() 连接状态

  • ?withFork() 叉状态

  • ?withJoin() 加入状态:需要在两个状态和转换中定义连接才能正常工作

  • ?Configuring Common Settings 配置通用设置

  • withEntry\withExit进入和退出,对进入和退出的过程设置更多的节点

1
2
3
4
5
6
7
8
9
//在状态配置中
.entry("S2ENTRY")
.exit("S2EXIT")
//在转换中
.withEntry()
.source("S2ENTRY").target("S22")
.and()
.withExit()
.source("S2EXIT").target("S3");
  • withModel() 配置模型:StateMachineModelFactory,通过自定义工厂模型

状态机ID 状态机实例的标志

1
2
.withConfiguration()
.machineId("mymachine");

状态机工厂EnableStateMachineFactory:工厂的配置通过config,编译限制

工厂的当前限制是所有动作和保护它与创建的状态机相关联将共享相同的实例

构建器模式StateMachineBuilder.builder:此构建器模式可用于在Spring应用程序上下文之外构建完全动态的状态机

目前builder.configureStates(),builder.configureTransitions() 和builder.configureConfiguration()接口方法不能被链接在一起意味着生成器方法需要单独调用。

1
2
3
4
5
6
7
8
9
10
11
StateMachine<String, String> buildMachine2() throws Exception {
Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.autoStartup(false)
.beanFactory(null)
.taskExecutor(null)
.taskScheduler(null)
.listener(null);
return builder.build();
}

重要的是要了解在从构建器实例化的机器需要使用的常见配置的情况

使用延期事件

1
2
3
4
5
6
7
8
9
10
11
12
13
//在状态配置中,事件DEPLOY推迟到DEPLOYPREPARE和DEPLOYEXECUTE
.state("DEPLOYPREPARE", "DEPLOY")
.state("DEPLOYEXECUTE", "DEPLOY");
//在转换配置中
.withExternal()
.source("READY").target("DEPLOYPREPARE")
.event("DEPLOY")
.and()
.withExternal()
.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
.and()
.withExternal()
.source("DEPLOYEXECUTE").target("READY");

在上面的状态机中,状态为READY,表示机器已准备好处理将使其进入实际部署的DEPLOY状态的事件。执行部署操作后,机器将返回READY状态。如果计算机正在使用同步执行程序,则在READY状态下发送多个事件 不会造成任何问题,因为事件发送会在事件调用之间阻塞。但是,如果执行程序正在使用线程,则其他事件可能会丢失,因为计算机不再处于可以处理事件的状态。因此推迟一些这些事件允许机器保存这些事件。

使用范围

对状态机中的作用域的支持非常有限,但可以使用普通的spring 注释来启用会话作用域@Scope,前提是@Bean

一旦你将状态机作为范围session,@Controller将它自动装入一个将为每个会话提供新的状态机实例。然后在HttpSession无效时销毁状态机。

在session作用域中使用状态机需要仔细规划,主要是因为它是一个相对较重的组件

Action操作

Action:执行转换之间的动作 可以设置进入状态与退出状态的动作

1
2
3
.stateEntry(States.S2, action(), errorAction())
.stateDo(States.S2, action(), errorAction())
.stateExit(States.S2, action(), errorAction())

可以直接将Action实现为匿名函数,也可以创建自己的实现并将适当的实现定义为bean

Guard防护

Guard防护保护状态转换,Guard接口用于评估方法的StateContext:用于判断是否可以转换

1
2
3
4
.withExternal()
.source(States.S2).target(States.S3)
.event(Events.E2)
.guardExpression("extendedState.variables.get('myvar')");

在上面的示例guardExpression中,只需检查扩展状态变量myvar是否为TRUE,是可以使用Spel表达式替代完整的Guard实现

扩展状态

StateContext有一个方法getExtendedState()返回一个接口ExtendedState,该接口提供对扩展状态变量的访问。您可以直接通过状态机或StateContext在从操作或转换回调期间访问变量

1
2
3
4
public void execute(StateContext<String, String> context) {
context.getExtendedState()
.getVariables().put("mykey", "myvalue");
}

如果需要获得扩展状态变量的通知:
1、使用StateMachineListener和收听extendedStateChanged(key, value)
2、为其实现Spring Application上下文侦听器 OnExtendedStateChanged

1
2
3
4
5
6
public class ExtendedStateVariableListener extends StateMachineListenerAdapter<String, String> {
@Override
public void extendedStateChanged(Object key, Object value) {
// do something with changed variable
}
}

使用StateContext

StateContext是使用状态机时最重要的对象之一,因为它被传递到各种方法和回调中,以提供状态机的当前状态及其可能的状态。如果稍微简化一下,可以将其视为当前状态机阶段的快照,其中传递 StateContext

作用:

访问当前Message,Event或者他们 MessageHeaders如果知道的话。
访问状态机Extended State。
获得StateMachine自己。
访问可能的状态机错误。
Transition如果适用, 访问当前。
访问状态机可能来自和前往的源和目标状态
Stage阶段

阶段是stage状态机当前与用户交互的表示。目前的阶段是EVENT_NOT_ACCEPTED,EXTENDED_STATE_CHANGED, STATE_CHANGED,STATE_ENTRY,STATE_EXIT,STATEMACHINE_ERROR, STATEMACHINE_START,STATEMACHINE_STOP,TRANSITION, TRANSITION_START和TRANSITION_END

触发过渡

通过由触发器触发的转换来完成驱动状态机。目前支持的触发器是EventTrigger和 TimerTrigger。

1
2
3
4
5
6
stateMachine.sendEvent(Events.E1);
Message<Events> message = MessageBuilder
.withPayload(Events.E2)
.setHeader("foo", "bar")
.build();
stateMachine.sendEvent(message);

在上面的例子中,我们使用两种不同的方式发送事件。首先,我们使用状态机api方法发送一个类型安全事件 sendEvent(E event)。其次,我们使用 带有自定义事件头的api方法发送包含在Spring消息消息中的sendEvent(Message message)事件。这允许用户使用事件添加任意额外信息,然后当用户正在实施操作时,StateContext可以看到该事件

TimerTrigger定时触发器:当需要在没有任何用户交互的情况下自动触发某些内容时, TimerTrigger非常有用

监听状态机事件

有两个选项,要么监听Spring应用程序上下文事件,要么直接将侦听器附加到状态机
1、应用程序上下文事件类是OnTransitionStartEvent, OnTransitionEvent,OnTransitionEndEvent,OnStateExitEvent, OnStateEntryEvent,OnStateChangedEvent,OnStateMachineStart和 OnStateMachineStop以及扩展基本事件类StateMachineEvent的其他类
2、实现监听器,并添加到状态机实例上

1
2
StateMachineEventListener listener = new StateMachineEventListener();
stateMachine.addStateListener(listener);

语境整合

通过监听事件或使用状态和转换的动作来与状态机进行交互有点受限。这种方法的时间太长,而且很难创建与状态机正在使用的应用程序的交互。对于这个特定的用例,我们进行了一种Spring样式上下文集成,可以轻松地将状态机功能附加到bean中

1
2
3
4
5
6
7
8
9
10
11
12
13
//在状态配置中
builder.configureConfiguration()
.withConfiguration()
.machineId("myMachineId")
.beanFactory(beanFactory);

//通过上下文集成状态机的配置
@WithStateMachine(id = "myMachineId")
static class Bean17 {
@OnStateChanged
public void onStateChanged() {
}
}

如果机器没有创建为Bean,则必须为机器设置 BeanFactory,如上所示。否则机器将不知道调用@WithStateMachine方法的处理程序

?状态机访问器

1
2
3
stateMachine.getStateMachineAccessor().doWithAllRegions(access -> access.setRelay(stateMachine));
stateMachine.getStateMachineAccessor().withAllRegions().stream().forEach(access -> access.setRelay(stateMachine));
stateMachine.getStateMachineAccessor().withRegion().setRelay(stateMachine);

恢复状态机后恢复状态

1
2
3
4
5
stateMachine.stop();
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access ->
access.resetStateMachine(new DefaultStateMachineContext<>(startingState, null, null, null)));
stateMachine.start();

StateMachineInterceptor拦截器

拦截器可用于拦截和停止当前状态变化或转换逻辑,还可以拦截到错误,通过访问器注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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) {
}

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

@Override
public Exception stateMachineError(StateMachine<String, String> stateMachine,
Exception exception) {
return exception;
}
});

?状态机安全 State Machine Security

1
2
3
4
.withSecurity()
.enabled(true)
.transitionAccessDecisionManager(null)
.eventAccessDecisionManager(null);

使用安全属性和表达式

状态机错误处理StateMachineInterceptor

如果状态机在状态转换逻辑期间检测到内部错误,则可能引发异常。在内部处理此异常之前,用户有机会进行拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
stateMachine.getStateMachineAccessor()
.doWithRegion(new StateMachineFunction<StateMachineAccess<String,String>>() {
@Override
public void apply(StateMachineAccess<String, String> function) {
function.addStateMachineInterceptor(
new StateMachineInterceptorAdapter<String, String>() {
@Override
public Exception stateMachineError(StateMachine<String,String> stateMachine,
Exception exception) {
// return null indicating handled error
return exception;
}
});
}
});

还可以整合监听器

1
2
3
4
5
6
7
8
public class ErrorApplicationEventListener
implements ApplicationListener<OnStateMachineError> {

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

持久化状态机 StateMachinePersister

持久性功能允许用户将状态机本身的状态保存到外部存储库中,然后根据序列化状态重置状态机。例如,如果您有一个数据库表保持订单,如果需要为每个更改构建一个新实例,那么通过状态机更新订单状态会太昂贵。持久性功能允许您重置状态机状态,而无需实例化新的状态机实例

可以使用状态机拦截器而不是在状态机内的状态改变期间完成将序列化状态保存到外部存储器的尝试。如果此拦截器回调失败,则状态更改尝试将暂停,而不是结束到不一致状态,然后用户可以手动处理此错误

状态机监控器 Monitoring State Machine

可用于获取有关转换和操作执行时间的持续时间的更多信息。

分布式状态机(还未成熟)

测试支持:一组实用程序类来轻松测试状态机实例

?存储支持:Repository Support,JPA\Redis\Mongo

状态机示例

  • 可以设置实例的范围:Scope是一个状态机示例,它使用会话范围为每个用户提供单独的实例。

  • 完全性:整合spring security

参考文献