天天即时:Spring Statemachine状态机的概念(四)

2022-12-20 14:18:18 来源:51CTO博客

状态机示例

参考文档的这一部分解释了状态的使用 机器以及示例代码和 UML 状态图。我们使用一些 表示状态图、Spring 状态机之间关系时的快捷方式 配置,以及应用程序对状态机执行的操作。为 完整的示例,您应该研究示例存储库。


【资料图】

样本是在 正常的构建周期。本章包括以下示例:

十字转门

旋转栅门反应

展示

CD播放器

任务

洗衣机

坚持

动物园管理员

范围

安全

活动服务

部署

订单运输

JPA 配置

数据持久化

数据 JPA 持久化

数据多保留

监测

下面的清单显示了如何生成示例:

./gradlew clean build -x test

每个示例都位于 下自己的目录中。这些示例基于 Spring Boot 和 弹簧外壳,您可以在每个样品下找到通常的 Boot 胖罐 项目的目录。​​spring-statemachine-samples​​​​build/libs​

我们在本节中引用的 jar 的文件名是在 构建此文档,这意味着,如果您从 主,您有带有后缀的文件。​​BUILD-SNAPSHOT​

十字转门

十字转门是一个简单的设备,如果付款是 䍬。这是一个使用状态机建模的简单概念。在其 最简单的形式只有两种状态:和。二 事件,并且可能发生,具体取决于某人是否 付款或尝试通过旋转栅门。 下图显示了状态机:​​LOCKED​​​​UNLOCKED​​​​COIN​​​​PUSH​

下面的清单显示了定义可能状态的枚举:

国家

public enum States {    LOCKED, UNLOCKED}

下面的清单显示了定义事件的枚举:

事件

public enum Events {    COIN, PUSH}

以下清单显示了配置状态机的代码:

配置

@Configuration@EnableStateMachinestatic class StateMachineConfig    extends EnumStateMachineConfigurerAdapter {  @Override  public void configure(StateMachineStateConfigurer states)      throws Exception {    states      .withStates()        .initial(States.LOCKED)        .states(EnumSet.allOf(States.class));  }  @Override  public void configure(StateMachineTransitionConfigurer transitions)      throws Exception {    transitions      .withExternal()        .source(States.LOCKED)        .target(States.UNLOCKED)        .event(Events.COIN)        .and()      .withExternal()        .source(States.UNLOCKED)        .target(States.LOCKED)        .event(Events.PUSH);  }}

您可以通过以下方式查看此示例状态机如何与事件交互 运行示例。以下清单显示了如何执行此操作 并显示命令的输出:​​turnstile​

$ java -jar spring-statemachine-samples-turnstile-3.2.0.jarsm>sm print+----------------------------------------------------------------+|                              SM                                |+----------------------------------------------------------------+|                                                                ||         +----------------+          +----------------+         ||     *-->|     LOCKED     |          |    UNLOCKED    |         ||         +----------------+          +----------------+         ||     +---| entry/         |          | entry/         |---+     ||     |   | exit/          |          | exit/          |   |     ||     |   |                |          |                |   |     || PUSH|   |                |---COIN-->|                |   |COIN ||     |   |                |          |                |   |     ||     |   |                |          |                |   |     ||     |   |                |<--PUSH---|                |   |     ||     +-->|                |          |                |<--+     ||         |                |          |                |         ||         +----------------+          +----------------+         ||                                                                |+----------------------------------------------------------------+sm>sm startState changed to LOCKEDState machine startedsm>sm event COINState changed to UNLOCKEDEvent COIN sendsm>sm event PUSHState changed to LOCKEDEvent PUSH send

旋转栅门反应

十字转门反应是对十字转门样本的增强使用 相同的状态机概念,并添加响应式 Web 层与 状态反应式接口。

​StateMachineController​​很简单,我们自动连接我们的.​​@RestController​​​​StateMachine​

@Autowiredprivate StateMachine stateMachine;

我们创建第一个映射来返回机器状态。由于状态没有从 一台机器被动地,我们可以推迟它,以便在订阅返回时, 请求实际状态。​​Mono​

@GetMapping("/state")public Mono state() {  return Mono.defer(() -> Mono.justOrEmpty(stateMachine.getState().getId()));}

要将单个事件或多个事件发送到机器,我们可以在两者中使用 传入和传出图层。 这里仅针对此示例,简单 包装和事件。​​Flux​​​​EventResult​​​​ResultType​

@PostMapping("/events")public Flux events(@RequestBody Flux eventData) {  return eventData    .filter(ed -> ed.getEvent() != null)    .map(ed -> MessageBuilder.withPayload(ed.getEvent()).build())    .flatMap(m -> stateMachine.sendEvent(Mono.just(m)))    .map(EventResult::new);}

可以使用以下命令运行示例:

$ java -jar spring-statemachine-samples-turnstilereactive-3.2.0.jar

获取状态的示例:

GET http://localhost:8080/state

然后会回应:

"LOCKED"

发送事件的示例:

POST http://localhost:8080/eventscontent-type: application/json{    "event": "COIN"}

然后会回应:

[  {    "event": "COIN",    "resultType": "ACCEPTED"  }]

您可以发布多个事件:

POST http://localhost:8080/eventscontent-type: application/json[    {        "event": "COIN"    },    {        "event": "PUSH"    }]

然后,响应包含两个事件的结果:

[  {    "event": "COIN",    "resultType": "ACCEPTED"  },  {    "event": "PUSH",    "resultType": "ACCEPTED"  }]

展示

Showcase 是一个复杂的状态机,显示所有可能的转换 拓扑最多四个级别的状态嵌套。 下图显示了状态机:

下面的清单显示了定义可能状态的枚举:

国家

public enum States {    S0, S1, S11, S12, S2, S21, S211, S212}

下面的清单显示了定义事件的枚举:

事件

public enum Events {    A, B, C, D, E, F, G, H, I}

以下清单显示了配置状态机的代码:

配置 - 状态

@Overridepublic void configure(StateMachineStateConfigurer states)    throws Exception {  states    .withStates()      .initial(States.S0, fooAction())      .state(States.S0)      .and()      .withStates()        .parent(States.S0)        .initial(States.S1)        .state(States.S1)        .and()        .withStates()          .parent(States.S1)          .initial(States.S11)          .state(States.S11)          .state(States.S12)          .and()      .withStates()        .parent(States.S0)        .state(States.S2)        .and()        .withStates()          .parent(States.S2)          .initial(States.S21)          .state(States.S21)          .and()          .withStates()            .parent(States.S21)            .initial(States.S211)            .state(States.S211)            .state(States.S212);}

以下清单显示了配置状态机转换的代码:

配置 - 转换

@Overridepublic void configure(StateMachineTransitionConfigurer transitions)    throws Exception {  transitions    .withExternal()      .source(States.S1).target(States.S1).event(Events.A)      .guard(foo1Guard())      .and()    .withExternal()      .source(States.S1).target(States.S11).event(Events.B)      .and()    .withExternal()      .source(States.S21).target(States.S211).event(Events.B)      .and()    .withExternal()      .source(States.S1).target(States.S2).event(Events.C)      .and()    .withExternal()      .source(States.S2).target(States.S1).event(Events.C)      .and()    .withExternal()      .source(States.S1).target(States.S0).event(Events.D)      .and()    .withExternal()      .source(States.S211).target(States.S21).event(Events.D)      .and()    .withExternal()      .source(States.S0).target(States.S211).event(Events.E)      .and()    .withExternal()      .source(States.S1).target(States.S211).event(Events.F)      .and()    .withExternal()      .source(States.S2).target(States.S11).event(Events.F)      .and()    .withExternal()      .source(States.S11).target(States.S211).event(Events.G)      .and()    .withExternal()      .source(States.S211).target(States.S0).event(Events.G)      .and()    .withInternal()      .source(States.S0).event(Events.H)      .guard(foo0Guard())      .action(fooAction())      .and()    .withInternal()      .source(States.S2).event(Events.H)      .guard(foo1Guard())      .action(fooAction())      .and()    .withInternal()      .source(States.S1).event(Events.H)      .and()    .withExternal()      .source(States.S11).target(States.S12).event(Events.I)      .and()    .withExternal()      .source(States.S211).target(States.S212).event(Events.I)      .and()    .withExternal()      .source(States.S12).target(States.S212).event(Events.I);}

以下清单显示了配置状态机的操作和防护的代码:

配置 - 操作和防护

@Beanpublic FooGuard foo0Guard() {  return new FooGuard(0);}@Beanpublic FooGuard foo1Guard() {  return new FooGuard(1);}@Beanpublic FooAction fooAction() {  return new FooAction();}

以下清单显示了如何定义单个操作:

行动

private static class FooAction implements Action {  @Override  public void execute(StateContext context) {    Map variables = context.getExtendedState().getVariables();    Integer foo = context.getExtendedState().get("foo", Integer.class);    if (foo == null) {      log.info("Init foo to 0");      variables.put("foo", 0);    } else if (foo == 0) {      log.info("Switch foo to 1");      variables.put("foo", 1);    } else if (foo == 1) {      log.info("Switch foo to 0");      variables.put("foo", 0);    }  }}

以下清单显示了如何定义单个防护:

警卫

private static class FooGuard implements Guard {  private final int match;  public FooGuard(int match) {    this.match = match;  }  @Override  public boolean evaluate(StateContext context) {    Object foo = context.getExtendedState().getVariables().get("foo");    return !(foo == null || !foo.equals(match));  }}

以下清单显示了此状态机在运行时生成的输出,并且 向其发送各种事件:

sm>sm startInit foo to 0Entry state S0Entry state S1Entry state S11State machine startedsm>sm event AEvent A sendsm>sm event CExit state S11Exit state S1Entry state S2Entry state S21Entry state S211Event C sendsm>sm event HSwitch foo to 1Internal transition source=S0Event H sendsm>sm event CExit state S211Exit state S21Exit state S2Entry state S1Entry state S11Event C sendsm>sm event AExit state S11Exit state S1Entry state S1Entry state S11Event A send

在前面的输出中,我们可以看到:

状态机启动,使其进入初始状态 () 通过超状态 () 和 ()。此外,扩展状态变量 是 初始化为 。S11S1S0foo0我们尝试在带有事件的状态中执行自我转换,但是 什么也没发生,因为转换由变量 to 保护 是。S1Afoo1我们发送事件,它将我们带到另一个状态机,其中 输入初始状态 () 及其超状态。在那里,我们 可以使用 event,它执行简单的内部转换来翻转变量。然后我们使用事件返回。CS211HfooC事件再次发送,现在执行自我转换,因为 守卫的计算结果为 。AS1true

以下示例详细介绍了分层状态及其事件 处理工程:

sm>sm variablesNo variablessm>sm startInit foo to 0Entry state S0Entry state S1Entry state S11State machine startedsm>sm variablesfoo=0sm>sm event HInternal transition source=S1Event H sendsm>sm variablesfoo=0sm>sm event CExit state S11Exit state S1Entry state S2Entry state S21Entry state S211Event C sendsm>sm variablesfoo=0sm>sm event HSwitch foo to 1Internal transition source=S0Event H sendsm>sm variablesfoo=1sm>sm event HSwitch foo to 0Internal transition source=S2Event H sendsm>sm variablesfoo=0

在前面的示例中:

我们在各个阶段打印扩展状态变量。对于事件,我们最终运行内部转换, 以其源状态记录。H注意事件是如何处理的 不同的状态(、 和 )。这是一个很好的例子,说明如何 分层状态及其事件处理工作。如果状态为 由于保护条件而无法处理事件,其父级是 检查下一个。这保证了,当机器处于状态时,标志 总是翻转。但是,在状态中,事件总是 匹配它的假过渡,没有警卫或行动,所以它永远不会 发生。HS0S1S2S2HS2fooS1H

CD播放器

CD 播放器是一个示例,类似于许多人拥有的用例 在现实世界中使用。CD播放器本身是一个非常简单的实体,它允许 用户打开卡组,插入或更换磁盘,然后驱动玩家的 通过按各种按钮(、、、、 和 )实现功能。​​eject​​​​play​​​​stop​​​​pause​​​​rewind​​​​backward​

我们中有多少人真正考虑过需要什么 制作与硬件交互的代码以驱动 CD 播放器。是的, 玩家的概念很简单,但是,如果你看看幕后, 事情实际上变得有点复杂。

您可能已经注意到,如果您的套牌打开并按播放, 甲板关闭并开始播放歌曲(如果插入了 CD)。 从某种意义上说,当甲板打开时,您首先需要关闭 然后尝试开始播放(同样,如果实际插入了 CD)。希望 您现在已经意识到一个简单的CD播放器是如此简单。 当然,你可以用一个简单的类来包装所有这些,这个类有几个布尔变量。 可能还有一些嵌套的 if-else 子句。这将完成这项工作,但是什么 关于您是否需要使所有这些行为变得更加复杂?是吗 真的想继续添加更多标志和 if-else 子句吗?

下图显示了简单 CD 播放器的状态机:

本节的其余部分将介绍此示例及其状态机的设计方式,以及 这两者如何相互作用。以下三个配置部分 在 中使用。​​EnumStateMachineConfigurerAdapter​

@Overridepublic void configure(StateMachineStateConfigurer states)    throws Exception {  states    .withStates()      .initial(States.IDLE)      .state(States.IDLE)      .and()      .withStates()        .parent(States.IDLE)        .initial(States.CLOSED)        .state(States.CLOSED, closedEntryAction(), null)        .state(States.OPEN)        .and()    .withStates()      .state(States.BUSY)      .and()      .withStates()        .parent(States.BUSY)        .initial(States.PLAYING)        .state(States.PLAYING)        .state(States.PAUSED);}
@Overridepublic void configure(StateMachineTransitionConfigurer transitions)    throws Exception {  transitions    .withExternal()      .source(States.CLOSED).target(States.OPEN).event(Events.EJECT)      .and()    .withExternal()      .source(States.OPEN).target(States.CLOSED).event(Events.EJECT)      .and()    .withExternal()      .source(States.OPEN).target(States.CLOSED).event(Events.PLAY)      .and()    .withExternal()      .source(States.PLAYING).target(States.PAUSED).event(Events.PAUSE)      .and()    .withInternal()      .source(States.PLAYING)      .action(playingAction())      .timer(1000)      .and()    .withInternal()      .source(States.PLAYING).event(Events.BACK)      .action(trackAction())      .and()    .withInternal()      .source(States.PLAYING).event(Events.FORWARD)      .action(trackAction())      .and()    .withExternal()      .source(States.PAUSED).target(States.PLAYING).event(Events.PAUSE)      .and()    .withExternal()      .source(States.BUSY).target(States.IDLE).event(Events.STOP)      .and()    .withExternal()      .source(States.IDLE).target(States.BUSY).event(Events.PLAY)      .action(playAction())      .guard(playGuard())      .and()    .withInternal()      .source(States.OPEN).event(Events.LOAD).action(loadAction());}
@Beanpublic ClosedEntryAction closedEntryAction() {  return new ClosedEntryAction();}@Beanpublic LoadAction loadAction() {  return new LoadAction();}@Beanpublic TrackAction trackAction() {  return new TrackAction();}@Beanpublic PlayAction playAction() {  return new PlayAction();}@Beanpublic PlayingAction playingAction() {  return new PlayingAction();}@Beanpublic PlayGuard playGuard() {  return new PlayGuard();}

在上述配置中:

我们曾经配置状态和 转换。EnumStateMachineConfigurerAdapter和状态定义为 和 的子状态 和状态定义为 的子状态。CLOSEDOPENIDLEPLAYINGPAUSEDBUSY对于状态,我们添加了一个条目操作作为称为 的 bean。CLOSEDclosedEntryAction在转换中,我们主要将事件映射到预期状态 过渡,例如关闭和打开甲板和 、 并进行自然过渡。对于其他转换,我们执行以下操作:EJECTPLAYSTOPPAUSE对于源状态,我们添加了一个计时器触发器,即 需要自动跟踪播放曲目中的经过时间,并且 有一个工具来决定何时切换到下一首曲目。PLAYING对于事件,如果源状态为 ,目标状态为 ,我们定义了一个调用的操作和一个名为 的守卫。PLAYIDLEBUSYplayActionplayGuard对于事件和状态,我们定义了一个内部 使用名为 的动作进行过渡,该动作跟踪插入光盘的过程 扩展状态变量。LOADOPENloadAction状态定义了三个内部转换。一个是 由运行名为 的操作的计时器触发,该操作更新 扩展状态变量。另外两个转换使用不同的事件(分别是 和 )来处理 当用户想要在轨道中后退或前进时。PLAYINGplayingActiontrackActionBACKFORWARD

此计算机只有六个状态,这些状态由以下枚举定义:

public enum States {    // super state of PLAYING and PAUSED    BUSY,    PLAYING,    PAUSED,    // super state of CLOSED and OPEN    IDLE,    CLOSED,    OPEN}

事件表示用户可以的按钮 按 和 用户是否将光盘加载到播放机中。 以下枚举定义事件:

public enum Events {    PLAY, STOP, PAUSE, EJECT, LOAD, FORWARD, BACK}

和 bean 用于驱动应用程序。 下面的清单显示了这两个 bean 的定义:​​cdPlayer​​​​library​

@Beanpublic CdPlayer cdPlayer() {  return new CdPlayer();}@Beanpublic Library library() {  return Library.buildSampleLibrary();}

我们将扩展状态变量键定义为简单的枚举, 如以下清单所示:

public enum Variables {  CD, TRACK, ELAPSEDTIME}public enum Headers {  TRACKSHIFT}

我们希望使这种样本类型安全,因此我们定义了自己的样本类型。 注释 (),它有一个必需的元 注释 ()。 下面的清单定义了批注:​​@StatesOnTransition​​​​@OnTransition​​​​@StatesOnTransition​

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@OnTransitionpublic @interface StatesOnTransition {  States[] source() default {};  States[] target() default {};}

​ClosedEntryAction​​是状态的入口操作,以 如果存在磁盘,则向状态机发送事件。 以下清单定义:​​CLOSED​​​​PLAY​​​​ClosedEntryAction​

public static class ClosedEntryAction implements Action {  @Override  public void execute(StateContext context) {    if (context.getTransition() != null        && context.getEvent() == Events.PLAY        && context.getTransition().getTarget().getId() == States.CLOSED        && context.getExtendedState().getVariables().get(Variables.CD) != null) {      context.getStateMachine()        .sendEvent(Mono.just(MessageBuilder          .withPayload(Events.PLAY).build()))        .subscribe();    }  }}

​LoadAction​​更新扩展状态变量 if 事件 标头包含有关要加载的光盘的信息。 以下清单定义:​​LoadAction​

public static class LoadAction implements Action {  @Override  public void execute(StateContext context) {    Object cd = context.getMessageHeader(Variables.CD);    context.getExtendedState().getVariables().put(Variables.CD, cd);  }}

​PlayAction​​重置玩家的经过时间,该时间保持为 扩展状态变量。 以下清单定义:​​PlayAction​

public static class PlayAction implements Action {  @Override  public void execute(StateContext context) {    context.getExtendedState().getVariables().put(Variables.ELAPSEDTIME, 0l);    context.getExtendedState().getVariables().put(Variables.TRACK, 0);  }}

​PlayGuard​​如果扩展状态变量不指示 光盘已加载。 以下清单定义:​​IDLE​​​​BUSY​​​​PLAY​​​​CD​​​​PlayGuard​

public static class PlayGuard implements Guard {  @Override  public boolean evaluate(StateContext context) {    ExtendedState extendedState = context.getExtendedState();    return extendedState.getVariables().get(Variables.CD) != null;  }}

​PlayingAction​​更新名为 的扩展状态变量 ,该变量 播放器可用于读取和更新其 LCD 状态显示。 也把手 当用户在轨道中后退或前进时进行轨道移位。 以下示例定义:​​ELAPSEDTIME​​​​PlayingAction​​​​PlayingAction​

public static class PlayingAction implements Action {  @Override  public void execute(StateContext context) {    Map variables = context.getExtendedState().getVariables();    Object elapsed = variables.get(Variables.ELAPSEDTIME);    Object cd = variables.get(Variables.CD);    Object track = variables.get(Variables.TRACK);    if (elapsed instanceof Long) {      long e = ((Long)elapsed) + 1000l;      if (e > ((Cd) cd).getTracks()[((Integer) track)].getLength()*1000) {        context.getStateMachine()          .sendEvent(Mono.just(MessageBuilder            .withPayload(Events.FORWARD)            .setHeader(Headers.TRACKSHIFT.toString(), 1).build()))          .subscribe();      } else {        variables.put(Variables.ELAPSEDTIME, e);      }    }  }}

​TrackAction​​处理用户后退或前进时的跟踪移位操作 在轨道中。如果曲目是光盘上的最后一个曲目,则停止播放并将事件发送到状态机。 以下示例定义:​​STOP​​​​TrackAction​

public static class TrackAction implements Action {  @Override  public void execute(StateContext context) {    Map variables = context.getExtendedState().getVariables();    Object trackshift = context.getMessageHeader(Headers.TRACKSHIFT.toString());    Object track = variables.get(Variables.TRACK);    Object cd = variables.get(Variables.CD);    if (trackshift instanceof Integer && track instanceof Integer && cd instanceof Cd) {      int next = ((Integer)track) + ((Integer)trackshift);      if (next >= 0 &&  ((Cd)cd).getTracks().length > next) {        variables.put(Variables.ELAPSEDTIME, 0l);        variables.put(Variables.TRACK, next);      } else if (((Cd)cd).getTracks().length <= next) {        context.getStateMachine()          .sendEvent(Mono.just(MessageBuilder            .withPayload(Events.STOP).build()))          .subscribe();      }    }  }}

状态机的另一个重要方面是它们具有 自己的责任(主要是围绕处理状态)并且所有应用程序 级别逻辑应保留在外部。这意味着应用程序需要 具有与状态机交互的方法。另外,请注意 我们用 注释,它指示 状态机,用于从您的 POJO 中查找方法,然后调用这些方法。 具有各种过渡。 以下示例显示它如何更新其 LCD 状态显示:​​CdPlayer​​​​@WithStateMachine​

@OnTransition(target = "BUSY")public void busy(ExtendedState extendedState) {  Object cd = extendedState.getVariables().get(Variables.CD);  if (cd != null) {    cdStatus = ((Cd)cd).getName();  }}

在前面的示例中,我们使用注释来挂钩回调 当发生目标状态为 .​​@OnTransition​​​​BUSY​

以下列表显示了我们的状态机如何处理播放器是否关闭:

@StatesOnTransition(target = {States.CLOSED, States.IDLE})public void closed(ExtendedState extendedState) {  Object cd = extendedState.getVariables().get(Variables.CD);  if (cd != null) {    cdStatus = ((Cd)cd).getName();  } else {    cdStatus = "No CD";  }  trackStatus = "";}

​@OnTransition​​(我们在前面的例子中使用)只能是 与枚举匹配的字符串一起使用。 允许您创建自己的使用实际枚举的类型安全批注。​​@StatesOnTransition​

以下示例显示了此状态机的实际工作方式。

sm>sm startEntry state IDLEEntry state CLOSEDState machine startedsm>cd lcdNo CDsm>cd library0: Greatest Hits  0: Bohemian Rhapsody  05:56  1: Another One Bites the Dust  03:361: Greatest Hits II  0: A Kind of Magic  04:22  1: Under Pressure  04:08sm>cd ejectExit state CLOSEDEntry state OPENsm>cd load 0Loading cd Greatest Hitssm>cd playExit state OPENEntry state CLOSEDExit state CLOSEDExit state IDLEEntry state BUSYEntry state PLAYINGsm>cd lcdGreatest Hits Bohemian Rhapsody 00:03sm>cd forwardsm>cd lcdGreatest Hits Another One Bites the Dust 00:04sm>cd stopExit state PLAYINGExit state BUSYEntry state IDLEEntry state CLOSEDsm>cd lcdGreatest Hits

在前面的运行中:

状态机启动,导致计算机初始化。将打印 CD 播放机的液晶屏状态。将打印 CD 库。CD 播放器的卡座已打开。索引为 0 的 CD 将加载到卡座中。播放会导致卡组关闭并立即播放,因为光盘 入。我们打印LCD状态并请求下一首曲目。我们停止播放。

任务

任务示例演示了 中的并行任务处理 区域,并向任一区域添加错误处理 自动或手动修复任务问题,然后再继续返回 到可以再次运行任务的状态。 下图显示了任务状态机:

在高级别上,在此状态机中:

我们总是试图进入状态,以便我们可以使用 运行事件以执行任务。READY由三个独立地区组成的Tkhe州一直是 放在和状态的中间,这将导致区域 进入其初始状态,并由其最终状态加入。TASKSFORKJOIN从状态,我们自动进入一个状态,检查 用于扩展状态变量中存在错误标志。任务可以设置 这些标志,这样做使状态能够进入状态,其中错误可以自动或手动处理。JOINCHOICECHOICEERROR状态可以尝试自动修复错误并进入 如果成功,请返回。如果错误是什么 无法自动处理,需要用户干预,并且 计算机由事件置于状态。AUTOMATICERRORREADYMANUALFALLBACK

下面的清单显示了定义可能状态的枚举:

国家

public enum States {    READY,    FORK, JOIN, CHOICE,    TASKS, T1, T1E, T2, T2E, T3, T3E,    ERROR, AUTOMATIC, MANUAL}

下面的清单显示了定义事件的枚举:

事件

public enum Events {    RUN, FALLBACK, CONTINUE, FIX;}

以下清单配置了可能的状态:

配置 - 状态

@Overridepublic void configure(StateMachineStateConfigurer states)    throws Exception {  states    .withStates()      .initial(States.READY)      .fork(States.FORK)      .state(States.TASKS)      .join(States.JOIN)      .choice(States.CHOICE)      .state(States.ERROR)      .and()      .withStates()        .parent(States.TASKS)        .initial(States.T1)        .end(States.T1E)        .and()      .withStates()        .parent(States.TASKS)        .initial(States.T2)        .end(States.T2E)        .and()      .withStates()        .parent(States.TASKS)        .initial(States.T3)        .end(States.T3E)        .and()      .withStates()        .parent(States.ERROR)        .initial(States.AUTOMATIC)        .state(States.AUTOMATIC, automaticAction(), null)        .state(States.MANUAL);}

以下清单配置了可能的转换:

配置 - 转换

@Overridepublic void configure(StateMachineTransitionConfigurer transitions)    throws Exception {  transitions    .withExternal()      .source(States.READY).target(States.FORK)      .event(Events.RUN)      .and()    .withFork()      .source(States.FORK).target(States.TASKS)      .and()    .withExternal()      .source(States.T1).target(States.T1E)      .and()    .withExternal()      .source(States.T2).target(States.T2E)      .and()    .withExternal()      .source(States.T3).target(States.T3E)      .and()    .withJoin()      .source(States.TASKS).target(States.JOIN)      .and()    .withExternal()      .source(States.JOIN).target(States.CHOICE)      .and()    .withChoice()      .source(States.CHOICE)      .first(States.ERROR, tasksChoiceGuard())      .last(States.READY)      .and()    .withExternal()      .source(States.ERROR).target(States.READY)      .event(Events.CONTINUE)      .and()    .withExternal()      .source(States.AUTOMATIC).target(States.MANUAL)      .event(Events.FALLBACK)      .and()    .withInternal()      .source(States.MANUAL)      .action(fixAction())      .event(Events.FIX);}

以下守卫将选择条目发送到状态中,需要 如果发生错误,则返回。此警卫检查 所有扩展状态变量 (、 和 ) 都是 。​​ERROR​​​​TRUE​​​​T1​​​​T2​​​​T3​​​​TRUE​

@Beanpublic Guard tasksChoiceGuard() {  return new Guard() {    @Override    public boolean evaluate(StateContext context) {      Map variables = context.getExtendedState().getVariables();      return !(ObjectUtils.nullSafeEquals(variables.get("T1"), true)          && ObjectUtils.nullSafeEquals(variables.get("T2"), true)          && ObjectUtils.nullSafeEquals(variables.get("T3"), true));    }  };}

以下操作将事件发送到状态机以请求 下一步,要么回退,要么继续回到就绪状态。

@Beanpublic Action automaticAction() {  return new Action() {    @Override    public void execute(StateContext context) {      Map variables = context.getExtendedState().getVariables();      if (ObjectUtils.nullSafeEquals(variables.get("T1"), true)          && ObjectUtils.nullSafeEquals(variables.get("T2"), true)          && ObjectUtils.nullSafeEquals(variables.get("T3"), true)) {        context.getStateMachine()          .sendEvent(Mono.just(MessageBuilder            .withPayload(Events.CONTINUE).build()))          .subscribe();      } else {        context.getStateMachine()          .sendEvent(Mono.just(MessageBuilder            .withPayload(Events.FALLBACK).build()))          .subscribe();      }    }  };}@Beanpublic Action fixAction() {  return new Action() {    @Override    public void execute(StateContext context) {      Map variables = context.getExtendedState().getVariables();      variables.put("T1", true);      variables.put("T2", true);      variables.put("T3", true);      context.getStateMachine()        .sendEvent(Mono.just(MessageBuilder          .withPayload(Events.CONTINUE).build()))        .subscribe();    }  };}

默认区域执行是同步的,这意味着将处理区域 顺序。在此示例中,我们只希望处理所有任务区域 平行。这可以通过定义来实现:​​RegionExecutionPolicy​

@Overridepublic void configure(StateMachineConfigurationConfigurer config)    throws Exception {  config    .withConfiguration()      .regionExecutionPolicy(RegionExecutionPolicy.PARALLEL);}

以下示例显示了此状态机的实际工作原理:

sm>sm startState machine startedEntry state READYsm>tasks runExit state READYEntry state TASKSrun task on T2run task on T1run task on T3run task on T2 donerun task on T1 donerun task on T3 doneEntry state T2Entry state T1Entry state T3Exit state T2Exit state T1Exit state T3Entry state T3EEntry state T1EEntry state T2EExit state TASKSEntry state READY

在前面的清单中,我们可以看到任务运行多次。 在下一个列表中,我们引入了错误:

sm>tasks listTasks {T1=true, T3=true, T2=true}sm>tasks fail T1sm>tasks listTasks {T1=false, T3=true, T2=true}sm>tasks runEntry state TASKSrun task on T1run task on T3run task on T2run task on T1 donerun task on T3 donerun task on T2 doneEntry state T1Entry state T3Entry state T2Entry state T1EEntry state T2EEntry state T3EExit state TASKSEntry state JOINExit state JOINEntry state ERROREntry state AUTOMATICExit state AUTOMATICExit state ERROREntry state READY

在前面的清单中,如果我们模拟任务 T1 的失败,则修复 自然而然。 在下一个列表中,我们引入了更多错误:

sm>tasks listTasks {T1=true, T3=true, T2=true}sm>tasks fail T2sm>tasks runEntry state TASKSrun task on T2run task on T1run task on T3run task on T2 donerun task on T1 donerun task on T3 doneEntry state T2Entry state T1Entry state T3Entry state T1EEntry state T2EEntry state T3EExit state TASKSEntry state JOINExit state JOINEntry state ERROREntry state AUTOMATICExit state AUTOMATICEntry state MANUALsm>tasks fixExit state MANUALExit state ERROREntry state READY

在前面的示例中,如果我们模拟任务或 的失败,则状态 机器进入需要手动修复问题的状态 在它回到状态之前。​​T2​​​​T3​​​​MANUAL​​​​READY​

洗衣机

洗衣机示例演示如何使用历史记录状态来恢复 在模拟断电情况下运行状态配置。

任何使用过洗衣机的人都知道,如果你以某种方式暂停 程序,当未暂停时,它从同一状态继续。 您可以使用 历史伪状态。 下图显示了垫圈的状态机:

下面的清单显示了定义可能状态的枚举:

国家

public enum States {    RUNNING, HISTORY, END,    WASHING, RINSING, DRYING,    POWEROFF}

下面的清单显示了定义事件的枚举:

事件

public enum Events {    RINSE, DRY, STOP,    RESTOREPOWER, CUTPOWER}

以下清单配置了可能的状态:

配置 - 状态

@Overridepublic void configure(StateMachineStateConfigurer states)    throws Exception {  states    .withStates()      .initial(States.RUNNING)      .state(States.POWEROFF)      .end(States.END)      .and()      .withStates()        .parent(States.RUNNING)        .initial(States.WASHING)        .state(States.RINSING)        .state(States.DRYING)        .history(States.HISTORY, History.SHALLOW);}

以下清单配置了可能的转换:

Configuration - transitions

@Overridepublic void configure(StateMachineTransitionConfigurer transitions)    throws Exception {  transitions    .withExternal()      .source(States.WASHING).target(States.RINSING)      .event(Events.RINSE)      .and()    .withExternal()      .source(States.RINSING).target(States.DRYING)      .event(Events.DRY)      .and()    .withExternal()      .source(States.RUNNING).target(States.POWEROFF)      .event(Events.CUTPOWER)      .and()    .withExternal()      .source(States.POWEROFF).target(States.HISTORY)      .event(Events.RESTOREPOWER)      .and()    .withExternal()      .source(States.RUNNING).target(States.END)      .event(Events.STOP);}

The following example shows how this state machine actually works:

sm>sm startEntry state RUNNINGEntry state WASHINGState machine startedsm>sm event RINSEExit state WASHINGEntry state RINSINGEvent RINSE sendsm>sm event DRYExit state RINSINGEntry state DRYINGEvent DRY sendsm>sm event CUTPOWERExit state DRYINGExit state RUNNINGEntry state POWEROFFEvent CUTPOWER sendsm>sm event RESTOREPOWERExit state POWEROFFEntry state RUNNINGEntry state WASHINGEntry state DRYINGEvent RESTOREPOWER send

在前面的运行中:

状态机启动,导致计算机初始化。状态机进入 RINSING 状态。状态机进入“正在干燥”状态。状态机切断电源并进入关机状态。状态从 HISTORY 状态恢复,这会收回状态机 到其先前的已知状态。

坚持

持久化是一个示例,它使用持久化配方来 演示如何由 状态机。

下图显示了状态机逻辑和配置:

以下清单显示了状态机配置:

状态机配置

@Configuration@EnableStateMachinestatic class StateMachineConfig    extends StateMachineConfigurerAdapter {  @Override  public void configure(StateMachineStateConfigurer states)      throws Exception {    states      .withStates()        .initial("PLACED")        .state("PROCESSING")        .state("SENT")        .state("DELIVERED");  }  @Override  public void configure(StateMachineTransitionConfigurer transitions)      throws Exception {    transitions      .withExternal()        .source("PLACED").target("PROCESSING")        .event("PROCESS")        .and()      .withExternal()        .source("PROCESSING").target("SENT")        .event("SEND")        .and()      .withExternal()        .source("SENT").target("DELIVERED")        .event("DELIVER");  }}

以下配置创建:​​PersistStateMachineHandler​

处理程序配置

@Configurationstatic class PersistHandlerConfig {  @Autowired  private StateMachine stateMachine;  @Bean  public Persist persist() {    return new Persist(persistStateMachineHandler());  }  @Bean  public PersistStateMachineHandler persistStateMachineHandler() {    return new PersistStateMachineHandler(stateMachine);  }}

下面的清单显示了与此示例一起使用的类:​​Order​

订单类

public static class Order {  int id;  String state;  public Order(int id, String state) {    this.id = id;    this.state = state;  }  @Override  public String toString() {    return "Order [id=" + id + ", state=" + state + "]";  }}

以下示例显示了状态机的输出:

sm>persist dbOrder [id=1, state=PLACED]Order [id=2, state=PROCESSING]Order [id=3, state=SENT]Order [id=4, state=DELIVERED]sm>persist process 1Exit state PLACEDEntry state PROCESSINGsm>persist dbOrder [id=2, state=PROCESSING]Order [id=3, state=SENT]Order [id=4, state=DELIVERED]Order [id=1, state=PROCESSING]sm>persist deliver 3Exit state SENTEntry state DELIVEREDsm>persist dbOrder [id=2, state=PROCESSING]Order [id=4, state=DELIVERED]Order [id=1, state=PROCESSING]Order [id=3, state=DELIVERED]

在前面的运行中,状态机:

列出现有嵌入式数据库中的行,该数据库已 填充了示例数据。请求将订单更新为状态。1PROCESSING再次列出数据库条目,并看到状态已从 更改为 。PLACEDPROCESSING更新顺序以将其状态从 更新到 。3SENTDELIVERED

您可能想知道数据库在哪里,因为实际上没有 示例代码中的标志。该示例基于 Spring Boot 和 因为必要的类位于类路径中,所以嵌入实例 是自动创建的。​​HSQL​

Spring Boot 甚至会创建一个实例,你 可以自动连线,如我们在 中所做的那样,如以下清单所示:​​JdbcTemplate​​​​Persist.java​

@Autowiredprivate JdbcTemplate jdbcTemplate;

接下来,我们需要处理状态更改。以下清单显示了我们如何做到这一点:

public void change(int order, String event) {  Order o = jdbcTemplate.queryForObject("select id, state from orders where id = ?",      new RowMapper() {        public Order mapRow(ResultSet rs, int rowNum) throws SQLException {          return new Order(rs.getInt("id"), rs.getString("state"));        }      }, new Object[] { order });  handler.handleEventWithStateReactively(MessageBuilder      .withPayload(event).setHeader("order", order).build(), o.state)    .subscribe();}

最后,我们使用 a 来更新数据库,因为 以下列表显示:​​PersistStateChangeListener​

private class LocalPersistStateChangeListener implements PersistStateChangeListener {  @Override  public void onPersist(State state, Message message,      Transition transition, StateMachine stateMachine) {    if (message != null && message.getHeaders().containsKey("order")) {      Integer order = message.getHeaders().get("order", Integer.class);      jdbcTemplate.update("update orders set state = ? where id = ?", state.getId(), order);    }  }}

动物园管理员

Zookeeper 是十字转门示例的分布式版本。

此示例需要一个可从其访问并具有默认端口和设置的外部实例。​​Zookeeper​​​​localhost​

此示例的配置与示例几乎相同。我们 仅添加分布式状态机的配置,其中我们 配置 ,如以下清单所示:​​turnstile​​​​StateMachineEnsemble​

@Overridepublic void configure(StateMachineConfigurationConfigurer config) throws Exception {  config    .withDistributed()      .ensemble(stateMachineEnsemble());}

实际需要一起创建为豆子 与客户端一起使用,如以下示例所示:​​StateMachineEnsemble​​​​CuratorFramework​

@Beanpublic StateMachineEnsemble stateMachineEnsemble() throws Exception {  return new ZookeeperStateMachineEnsemble(curatorClient(), "/foo");}@Beanpublic CuratorFramework curatorClient() throws Exception {  CuratorFramework client = CuratorFrameworkFactory.builder().defaultData(new byte[0])      .retryPolicy(new ExponentialBackoffRetry(1000, 3))      .connectString("localhost:2181").build();  client.start();  return client;}

对于下一个示例,我们需要创建两个不同的 shell 实例。 我们需要创建一个实例,看看会发生什么,然后创建第二个实例。 以下命令启动 shell 实例(请记住现在只启动一个实例):

@n1:~# java -jar spring-statemachine-samples-zookeeper-3.2.0.jar

启动状态机时,其初始状态为 。然后,它发送一个事件以转换为状态。 以下示例显示了发生的情况:​​LOCKED​​​​COIN​​​​UNLOCKED​

外壳1

sm>sm startEntry state LOCKEDState machine startedsm>sm event COINExit state LOCKEDEntry state UNLOCKEDEvent COIN sendsm>sm stateUNLOCKED

现在,您可以打开第二个 shell 实例并启动状态机, 使用用于启动第一个状态机的相同命令。你应该看到 输入分布式状态 () 而不是默认状态 初始状态 ()。​​UNLOCKED​​​​LOCKED​

以下示例显示了状态机及其输出:

外壳2

sm>sm startState machine startedsm>sm stateUNLOCKED

然后从任一 shell(我们在下一个示例中使用第二个实例)发送一个事件以从 进入状态。 以下示例显示了状态机命令及其输出:​​PUSH​​​​UNLOCKED​​​​LOCKED​

外壳2

sm>sm event PUSHExit state UNLOCKEDEntry state LOCKEDEvent PUSH send

在另一个外壳中(如果在第二个外壳中运行上述命令,则为第一个外壳), 您应该看到状态自动更改, 基于保存在动物园管理员中的分布式状态。 以下示例显示了状态机命令及其输出:

外壳1

sm>Exit state UNLOCKEDEntry state LOCKED

Web 是一个分布式状态机示例,它使用 zookeeper 状态机来处理 分布式状态。见动物园管理员。

此示例旨在在多个上运行 针对多个不同主机的浏览器会话。

此示例使用Showcase中修改的状态机结构来处理分布式状态 机器。下图显示了状态机逻辑:

由于此示例的性质,状态机的实例应 可从本地主机为每个单独的示例实例提供。​​Zookeeper​

此演示使用一个启动三个不同示例实例的示例。 如果在同一主机上运行不同的实例,则需要 通过添加到命令来区分每个端口。 否则,每个主机的默认端口为 。​​--server.port=​​​8080​

在此示例运行中,我们有三个主机:、 和 。每一个 正在运行本地 zookeeper 实例并运行状态机示例 在端口上。​​n1​​​​n2​​​​n3​​​​8080​

在不同的终端中,通过运行来启动三个不同的状态机 以下命令:

# java -jar spring-statemachine-samples-web-3.2.0.jar

当所有实例都运行时,您应该看到所有实例都显示相似 使用浏览器访问它们时的信息。状态应为 、 和 。 命名的扩展状态变量的值应为 。主要状态是 。​​S0​​​​S1​​​​S11​​​​foo​​​​0​​​​S11​

当您在任何浏览器窗口中按下该按钮时, 分布式状态更改为目标状态 由与类型事件关联的转换表示。 下图显示了更改:​​Event C​​​​S211,​​​​C​

现在我们可以按下按钮,看到 内部转换在所有状态机上运行,以更改 名为 to 的扩展状态变量的值。此更改是 首先在接收事件的状态机上完成,然后传播 到其他状态机。您应该只看到名为 change 的变量 从 到 。​​Event H​​​​foo​​​​0​​​​1​​​​foo​​​​0​​​​1​

最后,我们可以发送,它采取状态 机器状态恢复为状态。您应该会看到这种情况发生在 所有浏览器。下图显示了在一个浏览器中的结果:​​Event K​​​​S11​

范围

作用域是一个状态机示例,它使用会话作用域来提供 每个用户的独立实例。 下图显示了 Scope 状态机中的状态和事件:

这个简单的状态机有三种状态:、 和 。 它们之间的转换由三个事件控制:、 和 。​​S0​​​​S1​​​​S2​​​​A​​​​B​​​​C​

要启动状态机,请在终端中运行以下命令:

# java -jar spring-statemachine-samples-scope-3.2.0.jar

实例运行时,可以打开浏览器玩状态 机器。如果您在其他浏览器中打开同一页面,(例如,在 Chrome 和 Firefox 中的一个),你应该得到一个新的状态机 每个用户会话的实例。 下图显示了浏览器中的状态机:

安全

安全性是一个状态机示例,它使用大多数可能的组合 保护状态机。它保护发送事件、转换、 和行动。 下图显示了状态机的状态和事件:

要启动状态机,请运行以下命令:

# java -jar spring-statemachine-samples-secure-3.2.0.jar

我们通过要求用户具有 . Spring 安全性确保没有其他用户可以向其发送事件 状态机。 以下列表保护事件发送:​​USER​

@Overridepublic void configure(StateMachineConfigurationConfigurer config)    throws Exception {  config    .withConfiguration()      .autoStartup(true)      .and()    .withSecurity()      .enabled(true)      .event("hasRole("USER")");}

在此示例中,我们定义两个用户:

具有userUSER具有两个角色的名为的用户:和adminUSERADMIN

两个用户的密码都是 。 以下清单配置了这两个用户:​​password​

@EnableWebSecurity@EnableGlobalMethodSecurity(securedEnabled = true)static class SecurityConfig extends WebSecurityConfigurerAdapter {  @Autowired  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {    auth      .inMemoryAuthentication()        .withUser("user")          .password("password")          .roles("USER")          .and()        .withUser("admin")          .password("password")          .roles("USER", "ADMIN");  }}

我们根据状态图定义状态之间的各种转换 显示在示例的开头。只有具有活动角色的用户才能运行 和 之间的外部转换。同样,只有一个罐头 运行内部转换状态。 以下清单定义了转换,包括其安全性:​​ADMIN​​​​S2​​​​S3​​​​ADMIN​​​​S1​

@Overridepublic void configure(StateMachineTransitionConfigurer transitions)    throws Exception {  transitions    .withExternal()      .source(States.S0).target(States.S1).event(Events.A)      .and()    .withExternal()      .source(States.S1).target(States.S2).event(Events.B)      .and()    .withExternal()      .source(States.S2).target(States.S0).event(Events.C)      .and()    .withExternal()      .source(States.S2).target(States.S3).event(Events.E)      .secured("ROLE_ADMIN", ComparisonType.ANY)      .and()    .withExternal()      .source(States.S3).target(States.S0).event(Events.C)      .and()    .withInternal()      .source(States.S0).event(Events.D)      .action(adminAction())      .and()    .withInternal()      .source(States.S1).event(Events.F)      .action(transitionAction())      .secured("ROLE_ADMIN", ComparisonType.ANY);}

下面的清单使用一个名为的方法,其返回类型为 指定使用角色 :​​adminAction​​​​Action​​​​ADMIN​

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)@Beanpublic Action adminAction() {  return new Action() {    @Secured("ROLE_ADMIN")    @Override    public void execute(StateContext context) {      log.info("Executed only for admin role");    }  };}

以下内容在发送事件时运行状态的内部转换。​​Action​​​​S​​​​F​

@Beanpublic Action transitionAction() {  return new Action() {    @Override    public void execute(StateContext context) {      log.info("Executed only for admin role");    }  };}

过渡本身通过 的角色,因此如果当前用户 不讨厌那个角色。​​ADMIN​

活动服务

事件服务示例显示了如何将状态机概念用作 事件的处理引擎。此示例从一个问题演变而来:

我是否可以将 Spring 状态机用作微服务来将事件提供给 不同的状态机实例?其实弹簧状态机可以喂食 事件到可能数百万个不同的状态机实例。

此示例使用实例来持久化状态机 实例。​​Redis​

显然,JVM中的一百万个状态机实例将是 由于内存限制,这是一个坏主意。这导致 Spring 状态机的其他功能,允许您持久化和重用现有实例。​​StateMachineContext​

对于此示例,我们假设购物应用程序 将不同类型的事件发送到单独的 然后使用状态跟踪用户行为的微服务 机器。下图显示了状态模型,该模型具有几个状态 表示用户导航产品项列表,添加和删除 购物车中的商品、转到付款页面和发起付款 操作:​​PageView​

实际的购物应用程序会将这些事件发送到 此服务通过(例如)使用 REST 调用。更多关于这个 后。

请记住,此处的重点是拥有一个公开 API 的应用程序,用户可以使用该 API 发送可由 每个请求的状态机。​​REST​

以下状态机配置模拟了我们在 状态图。各种操作更新状态机以跟踪进入各种状态的条目数以及如何 很多时候,和 的内部转换被调用以及是否已执行:​​Extended State​​​​ADD​​​​DEL​​​​PAY​

@Bean(name = "stateMachineTarget")@Scope(scopeName="prototype")public StateMachine stateMachineTarget() throws Exception {  Builder builder = StateMachineBuilder.builder();  builder.configureConfiguration()    .withConfiguration()      .autoStartup(true);  builder.configureStates()    .withStates()      .initial(States.HOME)      .states(EnumSet.allOf(States.class));  builder.configureTransitions()    .withInternal()      .source(States.ITEMS).event(Events.ADD)      .action(addAction())      .and()    .withInternal()      .source(States.CART).event(Events.DEL)      .action(delAction())      .and()    .withInternal()      .source(States.PAYMENT).event(Events.PAY)      .action(payAction())      .and()    .withExternal()      .source(States.HOME).target(States.ITEMS)      .action(pageviewAction())      .event(Events.VIEW_I)      .and()    .withExternal()      .source(States.CART).target(States.ITEMS)      .action(pageviewAction())      .event(Events.VIEW_I)      .and()    .withExternal()      .source(States.ITEMS).target(States.CART)      .action(pageviewAction())      .event(Events.VIEW_C)      .and()    .withExternal()      .source(States.PAYMENT).target(States.CART)      .action(pageviewAction())      .event(Events.VIEW_C)      .and()    .withExternal()      .source(States.CART).target(States.PAYMENT)      .action(pageviewAction())      .event(Events.VIEW_P)      .and()    .withExternal()      .source(States.ITEMS).target(States.HOME)      .action(resetAction())      .event(Events.RESET)      .and()    .withExternal()      .source(States.CART).target(States.HOME)      .action(resetAction())      .event(Events.RESET)      .and()    .withExternal()      .source(States.PAYMENT).target(States.HOME)      .action(resetAction())      .event(Events.RESET);  return builder.build();}

暂时不要关注 OR,因为我们在本节后面会解释这些内容。​​stateMachineTarget​​​​@Scope​

我们设置了一个默认为 本地主机和默认端口。我们与实现一起使用。最后,我们创建一个使用先前 创建了豆子。​​RedisConnectionFactory​​​​StateMachinePersist​​​​RepositoryStateMachinePersist​​​​RedisStateMachinePersister​​​​StateMachinePersist​

然后将它们用于处理呼叫, 如以下清单所示:​​Controller​​​​REST​

@Beanpublic RedisConnectionFactory redisConnectionFactory() {  return new JedisConnectionFactory();}@Beanpublic StateMachinePersist stateMachinePersist(RedisConnectionFactory connectionFactory) {  RedisStateMachineContextRepository repository =      new RedisStateMachineContextRepository(connectionFactory);  return new RepositoryStateMachinePersist(repository);}@Beanpublic RedisStateMachinePersister redisStateMachinePersister(    StateMachinePersist stateMachinePersist) {  return new RedisStateMachinePersister(stateMachinePersist);}

我们创建一个名为 . 状态机实例化是一个相对 操作成本高昂,因此最好尝试池化实例 为每个请求实例化一个新实例。为此,我们首先 创建包装和池化 它的最大大小为三个。然后使用作用域代理此内容。实际上,这意味着 每个请求都从 豆厂。稍后,我们将展示如何使用这些实例。 下面的清单显示了我们如何创建和设置目标源:​​stateMachineTarget​​​​poolTargetSource​​​​stateMachineTarget​​​​poolTargetSource​​​​ProxyFactoryBean​​​​request​​​​REST​​​​ProxyFactoryBean​

@Bean@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)public ProxyFactoryBean stateMachine() {  ProxyFactoryBean pfb = new ProxyFactoryBean();  pfb.setTargetSource(poolTargetSource());  return pfb;}

下面的清单显示了我们设置最大大小并设置目标 Bean 名称:

@Beanpublic CommonsPool2TargetSource poolTargetSource() {  CommonsPool2TargetSource pool = new CommonsPool2TargetSource();  pool.setMaxSize(3);  pool.setTargetBeanName("stateMachineTarget");  return pool;}

现在我们可以进入实际演示了。您需要在 上运行一个 Redis 服务器 具有默认设置的本地主机。然后,您需要运行基于启动的示例 通过运行以下命令的应用程序:

# java -jar spring-statemachine-samples-eventservice-3.2.0.jar

在浏览器中,您会看到如下所示的内容:

在此 UI 中,可以使用三个用户:、 和 。 单击按钮将显示当前状态和扩展状态。启用 单击按钮之前的单选按钮会为此发送特定事件 用户。这种安排允许您使用 UI。​​joe​​​​bob​​​​dave​

在我们的 中,我们自动连线和 . 是作用域,所以你 为每个请求获取一个新实例,而 单例豆。 下面列出了自动连线和:​​StateMachineController​​​​StateMachine​​​​StateMachinePersister​​​​StateMachine​​​​request​​​​StateMachinePersist​​​​StateMachine​​​​StateMachinePersist​

@Autowiredprivate StateMachine stateMachine;@Autowiredprivate StateMachinePersister stateMachinePersister;

在下面的清单中,与 UI 一起使用以执行与 实际 API 可能会执行以下操作:​​feedAndGetState​​​​REST​

@RequestMapping("/state")public String feedAndGetState(@RequestParam(value = "user", required = false) String user,    @RequestParam(value = "id", required = false) Events id, Model model) throws Exception {  model.addAttribute("user", user);  model.addAttribute("allTypes", Events.values());  model.addAttribute("stateChartModel", stateChartModel);  // we may get into this page without a user so  // do nothing with a state machine  if (StringUtils.hasText(user)) {    resetStateMachineFromStore(user);    if (id != null) {      feedMachine(user, id);    }    model.addAttribute("states", stateMachine.getState().getIds());    model.addAttribute("extendedState", stateMachine.getExtendedState().getVariables());  }  return "states";}

在下面的清单中,是一种接受帖子的方法 JSON 内容。​​feedPageview​​​​REST​

@RequestMapping(value = "/feed",method= RequestMethod.POST)@ResponseStatus(HttpStatus.OK)public void feedPageview(@RequestBody(required = true) Pageview event) throws Exception {  Assert.notNull(event.getUser(), "User must be set");  Assert.notNull(event.getId(), "Id must be set");  resetStateMachineFromStore(event.getUser());  feedMachine(event.getUser(), event.getId());}

在下面的清单中,将事件发送到 并保留 其状态通过使用:​​feedMachine​​​​StateMachine​​​​StateMachinePersister​

private void feedMachine(String user, Events id) throws Exception {  stateMachine    .sendEvent(Mono.just(MessageBuilder      .withPayload(id).build()))    .blockLast();  stateMachinePersister.persist(stateMachine, "testprefix:" + user);}

以下清单显示了用于还原状态机的 对于特定用户:​​resetStateMachineFromStore​

private StateMachine resetStateMachineFromStore(String user) throws Exception {  return stateMachinePersister.restore(stateMachine, "testprefix:" + user);}

与通常使用 UI 发送事件一样,也可以使用调用来执行相同的操作, 如以下 curl 命令所示:​​REST​

# curl http://localhost:8080/feed -H "Content-Type: application/json" --data "{"user":"joe","id":"VIEW_I"}"

此时,您应该在 Redis 中具有键为 、 的内容,如以下示例所示:​​testprefix:joe​

$ ./redis-cli127.0.0.1:6379> KEYS *1) "testprefix:joe"

接下来的三个图像显示 的状态何时从 更改为 以及何时执行操作。​​joe​​​​HOME​​​​ITEMS​​​​ADD​

下图显示了正在发送的事件:​​ADD​

现在你还在状态,内部过渡导致 要增加到的扩展状态变量,如下图所示:​​ITEMS​​​​COUNT​​​​1​

现在,您可以运行以下 rest 调用几次(或通过 UI 进行),然后 查看每次调用时变量增加:​​curl​​​​COUNT​

# curl http://localhost:8080/feed -H "Content-Type: application/json" # --data "{"user":"joe","id":"ADD"}"

下图显示了这些操作的结果:

标签: 状态变量 应用程序

上一篇:天天快看:Spring Statemachine状态机的概念(五)
下一篇:生产环境 Redis 优化记录