天天即時:Spring Statemachine狀態(tài)機的概念(四)

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

狀態(tài)機示例

參考文檔的這一部分解釋了狀態(tài)的使用 機器以及示例代碼和 UML 狀態(tài)圖。我們使用一些 表示狀態(tài)圖、Spring 狀態(tài)機之間關(guān)系時的快捷方式 配置,以及應(yīng)用程序?qū)顟B(tài)機執(zhí)行的操作。為 完整的示例,您應(yīng)該研究示例存儲庫。


【資料圖】

樣本是在 正常的構(gòu)建周期。本章包括以下示例:

十字轉(zhuǎn)門

旋轉(zhuǎn)柵門反應(yīng)

展示

CD播放器

任務(wù)

洗衣機

堅持

動物園管理員

范圍

安全

活動服務(wù)

部署

訂單運輸

JPA 配置

數(shù)據(jù)持久化

數(shù)據(jù) JPA 持久化

數(shù)據(jù)多保留

監(jiān)測

下面的清單顯示了如何生成示例:

./gradlew clean build -x test

每個示例都位于 下自己的目錄中。這些示例基于 Spring Boot 和 彈簧外殼,您可以在每個樣品下找到通常的 Boot 胖罐 項目的目錄。??spring-statemachine-samples????build/libs??

我們在本節(jié)中引用的 jar 的文件名是在 構(gòu)建此文檔,這意味著,如果您從 主,您有帶有后綴的文件。??BUILD-SNAPSHOT??

十字轉(zhuǎn)門

十字轉(zhuǎn)門是一個簡單的設(shè)備,如果付款是 ?。這是一個使用狀態(tài)機建模的簡單概念。在其 最簡單的形式只有兩種狀態(tài):和。二 事件,并且可能發(fā)生,具體取決于某人是否 付款或嘗試通過旋轉(zhuǎn)柵門。 下圖顯示了狀態(tài)機:??LOCKED????UNLOCKED????COIN????PUSH??

下面的清單顯示了定義可能狀態(tài)的枚舉:

國家

public enum States {    LOCKED, UNLOCKED}

下面的清單顯示了定義事件的枚舉:

事件

public enum Events {    COIN, PUSH}

以下清單顯示了配置狀態(tài)機的代碼:

配置

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

您可以通過以下方式查看此示例狀態(tài)機如何與事件交互 運行示例。以下清單顯示了如何執(zhí)行此操作 并顯示命令的輸出:??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

旋轉(zhuǎn)柵門反應(yīng)

十字轉(zhuǎn)門反應(yīng)是對十字轉(zhuǎn)門樣本的增強使用 相同的狀態(tài)機概念,并添加響應(yīng)式 Web 層與 狀態(tài)反應(yīng)式接口。

??StateMachineController??很簡單,我們自動連接我們的.??@RestController????StateMachine??

@Autowiredprivate StateMachine stateMachine;

我們創(chuàng)建第一個映射來返回機器狀態(tài)。由于狀態(tài)沒有從 一臺機器被動地,我們可以推遲它,以便在訂閱返回時, 請求實際狀態(tài)。??Mono??

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

要將單個事件或多個事件發(fā)送到機器,我們可以在兩者中使用 傳入和傳出圖層。 這里僅針對此示例,簡單 包裝和事件。??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

獲取狀態(tài)的示例:

GET http://localhost:8080/state

然后會回應(yīng):

"LOCKED"

發(fā)送事件的示例:

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

然后會回應(yīng):

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

您可以發(fā)布多個事件:

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

然后,響應(yīng)包含兩個事件的結(jié)果:

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

展示

Showcase 是一個復(fù)雜的狀態(tài)機,顯示所有可能的轉(zhuǎn)換 拓撲最多四個級別的狀態(tài)嵌套。 下圖顯示了狀態(tài)機:

下面的清單顯示了定義可能狀態(tài)的枚舉:

國家

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

下面的清單顯示了定義事件的枚舉:

事件

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

以下清單顯示了配置狀態(tài)機的代碼:

配置 - 狀態(tà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);}

以下清單顯示了配置狀態(tài)機轉(zhuǎn)換的代碼:

配置 - 轉(zhuǎn)換

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

以下清單顯示了配置狀態(tà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);    }  }}

以下清單顯示了如何定義單個防護:

警衛(wèi)

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));  }}

以下清單顯示了此狀態(tài)機在運行時生成的輸出,并且 向其發(fā)送各種事件:

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

在前面的輸出中,我們可以看到:

狀態(tài)機啟動,使其進入初始狀態(tài) () 通過超狀態(tài) () 和 ()。此外,擴展狀態(tài)變量 是 初始化為 。S11S1S0foo0我們嘗試在帶有事件的狀態(tài)中執(zhí)行自我轉(zhuǎn)換,但是 什么也沒發(fā)生,因為轉(zhuǎn)換由變量 to 保護 是。S1Afoo1我們發(fā)送事件,它將我們帶到另一個狀態(tài)機,其中 輸入初始狀態(tài) () 及其超狀態(tài)。在那里,我們 可以使用 event,它執(zhí)行簡單的內(nèi)部轉(zhuǎn)換來翻轉(zhuǎn)變量。然后我們使用事件返回。CS211HfooC事件再次發(fā)送,現(xiàn)在執(zhí)行自我轉(zhuǎn)換,因為 守衛(wèi)的計算結(jié)果為 。AS1true

以下示例詳細介紹了分層狀態(tài)及其事件 處理工程:

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

在前面的示例中:

我們在各個階段打印擴展狀態(tài)變量。對于事件,我們最終運行內(nèi)部轉(zhuǎn)換, 以其源狀態(tài)記錄。H注意事件是如何處理的 不同的狀態(tài)(、 和 )。這是一個很好的例子,說明如何 分層狀態(tài)及其事件處理工作。如果狀態(tài)為 由于保護條件而無法處理事件,其父級是 檢查下一個。這保證了,當機器處于狀態(tài)時,標志 總是翻轉(zhuǎn)。但是,在狀態(tài)中,事件總是 匹配它的假過渡,沒有警衛(wèi)或行動,所以它永遠不會 發(fā)生。HS0S1S2S2HS2fooS1H

CD播放器

CD 播放器是一個示例,類似于許多人擁有的用例 在現(xiàn)實世界中使用。CD播放器本身是一個非常簡單的實體,它允許 用戶打開卡組,插入或更換磁盤,然后驅(qū)動玩家的 通過按各種按鈕(、、、、 和 )實現(xiàn)功能。??eject????play????stop????pause????rewind????backward??

我們中有多少人真正考慮過需要什么 制作與硬件交互的代碼以驅(qū)動 CD 播放器。是的, 玩家的概念很簡單,但是,如果你看看幕后, 事情實際上變得有點復(fù)雜。

您可能已經(jīng)注意到,如果您的套牌打開并按播放, 甲板關(guān)閉并開始播放歌曲(如果插入了 CD)。 從某種意義上說,當甲板打開時,您首先需要關(guān)閉 然后嘗試開始播放(同樣,如果實際插入了 CD)。希望 您現(xiàn)在已經(jīng)意識到一個簡單的CD播放器是如此簡單。 當然,你可以用一個簡單的類來包裝所有這些,這個類有幾個布爾變量。 可能還有一些嵌套的 if-else 子句。這將完成這項工作,但是什么 關(guān)于您是否需要使所有這些行為變得更加復(fù)雜?是嗎 真的想繼續(xù)添加更多標志和 if-else 子句嗎?

下圖顯示了簡單 CD 播放器的狀態(tài)機:

本節(jié)的其余部分將介紹此示例及其狀態(tài)機的設(shè)計方式,以及 這兩者如何相互作用。以下三個配置部分 在 中使用。??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();}

在上述配置中:

我們曾經(jīng)配置狀態(tài)和 轉(zhuǎn)換。EnumStateMachineConfigurerAdapter和狀態(tài)定義為 和 的子狀態(tài) 和狀態(tài)定義為 的子狀態(tài)。CLOSEDOPENIDLEPLAYINGPAUSEDBUSY對于狀態(tài),我們添加了一個條目操作作為稱為 的 bean。CLOSEDclosedEntryAction在轉(zhuǎn)換中,我們主要將事件映射到預(yù)期狀態(tài) 過渡,例如關(guān)閉和打開甲板和 、 并進行自然過渡。對于其他轉(zhuǎn)換,我們執(zhí)行以下操作:EJECTPLAYSTOPPAUSE對于源狀態(tài),我們添加了一個計時器觸發(fā)器,即 需要自動跟蹤播放曲目中的經(jīng)過時間,并且 有一個工具來決定何時切換到下一首曲目。PLAYING對于事件,如果源狀態(tài)為 ,目標狀態(tài)為 ,我們定義了一個調(diào)用的操作和一個名為 的守衛(wèi)。PLAYIDLEBUSYplayActionplayGuard對于事件和狀態(tài),我們定義了一個內(nèi)部 使用名為 的動作進行過渡,該動作跟蹤插入光盤的過程 擴展狀態(tài)變量。LOADOPENloadAction狀態(tài)定義了三個內(nèi)部轉(zhuǎn)換。一個是 由運行名為 的操作的計時器觸發(fā),該操作更新 擴展狀態(tài)變量。另外兩個轉(zhuǎn)換使用不同的事件(分別是 和 )來處理 當用戶想要在軌道中后退或前進時。PLAYINGplayingActiontrackActionBACKFORWARD

此計算機只有六個狀態(tài),這些狀態(tài)由以下枚舉定義:

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 用于驅(qū)動應(yīng)用程序。 下面的清單顯示了這兩個 bean 的定義:??cdPlayer????library??

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

我們將擴展狀態(tài)變量鍵定義為簡單的枚舉, 如以下清單所示:

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??是狀態(tài)的入口操作,以 如果存在磁盤,則向狀態(tài)機發(fā)送事件。 以下清單定義:??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??更新擴展狀態(tài)變量 if 事件 標頭包含有關(guān)要加載的光盤的信息。 以下清單定義:??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??重置玩家的經(jīng)過時間,該時間保持為 擴展狀態(tài)變量。 以下清單定義:??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??如果擴展狀態(tài)變量不指示 光盤已加載。 以下清單定義:??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??更新名為 的擴展狀態(tài)變量 ,該變量 播放器可用于讀取和更新其 LCD 狀態(tài)顯示。 也把手 當用戶在軌道中后退或前進時進行軌道移位。 以下示例定義:??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??處理用戶后退或前進時的跟蹤移位操作 在軌道中。如果曲目是光盤上的最后一個曲目,則停止播放并將事件發(fā)送到狀態(tài)機。 以下示例定義:??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();      }    }  }}

狀態(tài)機的另一個重要方面是它們具有 自己的責(zé)任(主要是圍繞處理狀態(tài))并且所有應(yīng)用程序 級別邏輯應(yīng)保留在外部。這意味著應(yīng)用程序需要 具有與狀態(tài)機交互的方法。另外,請注意 我們用 注釋,它指示 狀態(tài)機,用于從您的 POJO 中查找方法,然后調(diào)用這些方法。 具有各種過渡。 以下示例顯示它如何更新其 LCD 狀態(tài)顯示:??CdPlayer????@WithStateMachine??

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

在前面的示例中,我們使用注釋來掛鉤回調(diào) 當發(fā)生目標狀態(tài)為 .??@OnTransition????BUSY??

以下列表顯示了我們的狀態(tài)機如何處理播放器是否關(guān)閉:

@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??(我們在前面的例子中使用)只能是 與枚舉匹配的字符串一起使用。 允許您創(chuàng)建自己的使用實際枚舉的類型安全批注。??@StatesOnTransition??

以下示例顯示了此狀態(tài)機的實際工作方式。

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

在前面的運行中:

狀態(tài)機啟動,導(dǎo)致計算機初始化。將打印 CD 播放機的液晶屏狀態(tài)。將打印 CD 庫。CD 播放器的卡座已打開。索引為 0 的 CD 將加載到卡座中。播放會導(dǎo)致卡組關(guān)閉并立即播放,因為光盤 入。我們打印LCD狀態(tài)并請求下一首曲目。我們停止播放。

任務(wù)

任務(wù)示例演示了 中的并行任務(wù)處理 區(qū)域,并向任一區(qū)域添加錯誤處理 自動或手動修復(fù)任務(wù)問題,然后再繼續(xù)返回 到可以再次運行任務(wù)的狀態(tài)。 下圖顯示了任務(wù)狀態(tài)機:

在高級別上,在此狀態(tài)機中:

我們總是試圖進入狀態(tài),以便我們可以使用 運行事件以執(zhí)行任務(wù)。READY由三個獨立地區(qū)組成的Tkhe州一直是 放在和狀態(tài)的中間,這將導(dǎo)致區(qū)域 進入其初始狀態(tài),并由其最終狀態(tài)加入。TASKSFORKJOIN從狀態(tài),我們自動進入一個狀態(tài),檢查 用于擴展狀態(tài)變量中存在錯誤標志。任務(wù)可以設(shè)置 這些標志,這樣做使狀態(tài)能夠進入狀態(tài),其中錯誤可以自動或手動處理。JOINCHOICECHOICEERROR狀態(tài)可以嘗試自動修復(fù)錯誤并進入 如果成功,請返回。如果錯誤是什么 無法自動處理,需要用戶干預(yù),并且 計算機由事件置于狀態(tài)。AUTOMATICERRORREADYMANUALFALLBACK

下面的清單顯示了定義可能狀態(tài)的枚舉:

國家

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

下面的清單顯示了定義事件的枚舉:

事件

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

以下清單配置了可能的狀態(tài):

配置 - 狀態(tài)

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

以下清單配置了可能的轉(zhuǎn)換:

配置 - 轉(zhuǎn)換

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

以下守衛(wèi)將選擇條目發(fā)送到狀態(tài)中,需要 如果發(fā)生錯誤,則返回。此警衛(wèi)檢查 所有擴展狀態(tài)變量 (、 和 ) 都是 。??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));    }  };}

以下操作將事件發(fā)送到狀態(tài)機以請求 下一步,要么回退,要么繼續(xù)回到就緒狀態(tài)。

@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();    }  };}

默認區(qū)域執(zhí)行是同步的,這意味著將處理區(qū)域 順序。在此示例中,我們只希望處理所有任務(wù)區(qū)域 平行。這可以通過定義來實現(xiàn):??RegionExecutionPolicy??

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

以下示例顯示了此狀態(tài)機的實際工作原理:

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

在前面的清單中,我們可以看到任務(wù)運行多次。 在下一個列表中,我們引入了錯誤:

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

在前面的清單中,如果我們模擬任務(wù) T1 的失敗,則修復(fù) 自然而然。 在下一個列表中,我們引入了更多錯誤:

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

在前面的示例中,如果我們模擬任務(wù)或 的失敗,則狀態(tài) 機器進入需要手動修復(fù)問題的狀態(tài) 在它回到狀態(tài)之前。??T2????T3????MANUAL????READY??

洗衣機

洗衣機示例演示如何使用歷史記錄狀態(tài)來恢復(fù) 在模擬斷電情況下運行狀態(tài)配置。

任何使用過洗衣機的人都知道,如果你以某種方式暫停 程序,當未暫停時,它從同一狀態(tài)繼續(xù)。 您可以使用 歷史偽狀態(tài)。 下圖顯示了墊圈的狀態(tài)機:

下面的清單顯示了定義可能狀態(tài)的枚舉:

國家

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

下面的清單顯示了定義事件的枚舉:

事件

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

以下清單配置了可能的狀態(tài):

配置 - 狀態(tài)

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

以下清單配置了可能的轉(zhuǎn)換:

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

在前面的運行中:

狀態(tài)機啟動,導(dǎo)致計算機初始化。狀態(tài)機進入 RINSING 狀態(tài)。狀態(tài)機進入“正在干燥”狀態(tài)。狀態(tài)機切斷電源并進入關(guān)機狀態(tài)。狀態(tài)從 HISTORY 狀態(tài)恢復(fù),這會收回狀態(tài)機 到其先前的已知狀態(tài)。

堅持

持久化是一個示例,它使用持久化配方來 演示如何由 狀態(tài)機。

下圖顯示了狀態(tài)機邏輯和配置:

以下清單顯示了狀態(tài)機配置:

狀態(tài)機配置

@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");  }}

以下配置創(chuàng)建:??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 + "]";  }}

以下示例顯示了狀態(tài)機的輸出:

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]

在前面的運行中,狀態(tài)機:

列出現(xiàn)有嵌入式數(shù)據(jù)庫中的行,該數(shù)據(jù)庫已 填充了示例數(shù)據(jù)。請求將訂單更新為狀態(tài)。1PROCESSING再次列出數(shù)據(jù)庫條目,并看到狀態(tài)已從 更改為 。PLACEDPROCESSING更新順序以將其狀態(tài)從 更新到 。3SENTDELIVERED

您可能想知道數(shù)據(jù)庫在哪里,因為實際上沒有 示例代碼中的標志。該示例基于 Spring Boot 和 因為必要的類位于類路徑中,所以嵌入實例 是自動創(chuàng)建的。??HSQL??

Spring Boot 甚至?xí)?chuàng)建一個實例,你 可以自動連線,如我們在 中所做的那樣,如以下清單所示:??JdbcTemplate????Persist.java??

@Autowiredprivate JdbcTemplate jdbcTemplate;

接下來,我們需要處理狀態(tài)更改。以下清單顯示了我們?nèi)绾巫龅竭@一點:

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 來更新數(shù)據(jù)庫,因為 以下列表顯示:??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 是十字轉(zhuǎn)門示例的分布式版本。

此示例需要一個可從其訪問并具有默認端口和設(shè)置的外部實例。??Zookeeper????localhost??

此示例的配置與示例幾乎相同。我們 僅添加分布式狀態(tài)機的配置,其中我們 配置 ,如以下清單所示:??turnstile????StateMachineEnsemble??

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

實際需要一起創(chuàng)建為豆子 與客戶端一起使用,如以下示例所示:??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;}

對于下一個示例,我們需要創(chuàng)建兩個不同的 shell 實例。 我們需要創(chuàng)建一個實例,看看會發(fā)生什么,然后創(chuàng)建第二個實例。 以下命令啟動 shell 實例(請記住現(xiàn)在只啟動一個實例):

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

啟動狀態(tài)機時,其初始狀態(tài)為 。然后,它發(fā)送一個事件以轉(zhuǎn)換為狀態(tài)。 以下示例顯示了發(fā)生的情況:??LOCKED????COIN????UNLOCKED??

外殼1

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

現(xiàn)在,您可以打開第二個 shell 實例并啟動狀態(tài)機, 使用用于啟動第一個狀態(tài)機的相同命令。你應(yīng)該看到 輸入分布式狀態(tài) () 而不是默認狀態(tài) 初始狀態(tài) ()。??UNLOCKED????LOCKED??

以下示例顯示了狀態(tài)機及其輸出:

外殼2

sm>sm startState machine startedsm>sm stateUNLOCKED

然后從任一 shell(我們在下一個示例中使用第二個實例)發(fā)送一個事件以從 進入狀態(tài)。 以下示例顯示了狀態(tài)機命令及其輸出:??PUSH????UNLOCKED????LOCKED??

外殼2

sm>sm event PUSHExit state UNLOCKEDEntry state LOCKEDEvent PUSH send

在另一個外殼中(如果在第二個外殼中運行上述命令,則為第一個外殼), 您應(yīng)該看到狀態(tài)自動更改, 基于保存在動物園管理員中的分布式狀態(tài)。 以下示例顯示了狀態(tài)機命令及其輸出:

外殼1

sm>Exit state UNLOCKEDEntry state LOCKED

Web 是一個分布式狀態(tài)機示例,它使用 zookeeper 狀態(tài)機來處理 分布式狀態(tài)。見動物園管理員。

此示例旨在在多個上運行 針對多個不同主機的瀏覽器會話。

此示例使用Showcase中修改的狀態(tài)機結(jié)構(gòu)來處理分布式狀態(tài) 機器。下圖顯示了狀態(tài)機邏輯:

由于此示例的性質(zhì),狀態(tài)機的實例應(yīng) 可從本地主機為每個單獨的示例實例提供。??Zookeeper??

此演示使用一個啟動三個不同示例實例的示例。 如果在同一主機上運行不同的實例,則需要 通過添加到命令來區(qū)分每個端口。 否則,每個主機的默認端口為 。??--server.port=????8080??

在此示例運行中,我們有三個主機:、 和 。每一個 正在運行本地 zookeeper 實例并運行狀態(tài)機示例 在端口上。??n1????n2????n3????8080??

在不同的終端中,通過運行來啟動三個不同的狀態(tài)機 以下命令:

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

當所有實例都運行時,您應(yīng)該看到所有實例都顯示相似 使用瀏覽器訪問它們時的信息。狀態(tài)應(yīng)為 、 和 。 命名的擴展狀態(tài)變量的值應(yīng)為 。主要狀態(tài)是 。??S0????S1????S11????foo????0????S11??

當您在任何瀏覽器窗口中按下該按鈕時, 分布式狀態(tài)更改為目標狀態(tài) 由與類型事件關(guān)聯(lián)的轉(zhuǎn)換表示。 下圖顯示了更改:??Event C????S211,????C??

現(xiàn)在我們可以按下按鈕,看到 內(nèi)部轉(zhuǎn)換在所有狀態(tài)機上運行,以更改 名為 to 的擴展狀態(tài)變量的值。此更改是 首先在接收事件的狀態(tài)機上完成,然后傳播 到其他狀態(tài)機。您應(yīng)該只看到名為 change 的變量 從 到 。??Event H????foo????0????1????foo????0????1??

最后,我們可以發(fā)送,它采取狀態(tài) 機器狀態(tài)恢復(fù)為狀態(tài)。您應(yīng)該會看到這種情況發(fā)生在 所有瀏覽器。下圖顯示了在一個瀏覽器中的結(jié)果:??Event K????S11??

范圍

作用域是一個狀態(tài)機示例,它使用會話作用域來提供 每個用戶的獨立實例。 下圖顯示了 Scope 狀態(tài)機中的狀態(tài)和事件:

這個簡單的狀態(tài)機有三種狀態(tài):、 和 。 它們之間的轉(zhuǎn)換由三個事件控制:、 和 。??S0????S1????S2????A????B????C??

要啟動狀態(tài)機,請在終端中運行以下命令:

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

實例運行時,可以打開瀏覽器玩狀態(tài) 機器。如果您在其他瀏覽器中打開同一頁面,(例如,在 Chrome 和 Firefox 中的一個),你應(yīng)該得到一個新的狀態(tài)機 每個用戶會話的實例。 下圖顯示了瀏覽器中的狀態(tài)機:

安全

安全性是一個狀態(tài)機示例,它使用大多數(shù)可能的組合 保護狀態(tài)機。它保護發(fā)送事件、轉(zhuǎn)換、 和行動。 下圖顯示了狀態(tài)機的狀態(tài)和事件:

要啟動狀態(tài)機,請運行以下命令:

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

我們通過要求用戶具有 . Spring 安全性確保沒有其他用戶可以向其發(fā)送事件 狀態(tài)機。 以下列表保護事件發(fā)送:??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");  }}

我們根據(jù)狀態(tài)圖定義狀態(tài)之間的各種轉(zhuǎn)換 顯示在示例的開頭。只有具有活動角色的用戶才能運行 和 之間的外部轉(zhuǎn)換。同樣,只有一個罐頭 運行內(nèi)部轉(zhuǎn)換狀態(tài)。 以下清單定義了轉(zhuǎn)換,包括其安全性:??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");    }  };}

以下內(nèi)容在發(fā)送事件時運行狀態(tài)的內(nèi)部轉(zhuǎn)換。??Action????S????F??

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

過渡本身通過 的角色,因此如果當前用戶 不討厭那個角色。??ADMIN??

活動服務(wù)

事件服務(wù)示例顯示了如何將狀態(tài)機概念用作 事件的處理引擎。此示例從一個問題演變而來:

我是否可以將 Spring 狀態(tài)機用作微服務(wù)來將事件提供給 不同的狀態(tài)機實例?其實彈簧狀態(tài)機可以喂食 事件到可能數(shù)百萬個不同的狀態(tài)機實例。

此示例使用實例來持久化狀態(tài)機 實例。??Redis??

顯然,JVM中的一百萬個狀態(tài)機實例將是 由于內(nèi)存限制,這是一個壞主意。這導(dǎo)致 Spring 狀態(tài)機的其他功能,允許您持久化和重用現(xiàn)有實例。??StateMachineContext??

對于此示例,我們假設(shè)購物應(yīng)用程序 將不同類型的事件發(fā)送到單獨的 然后使用狀態(tài)跟蹤用戶行為的微服務(wù) 機器。下圖顯示了狀態(tài)模型,該模型具有幾個狀態(tài) 表示用戶導(dǎo)航產(chǎn)品項列表,添加和刪除 購物車中的商品、轉(zhuǎn)到付款頁面和發(fā)起付款 操作:??PageView??

實際的購物應(yīng)用程序會將這些事件發(fā)送到 此服務(wù)通過(例如)使用 REST 調(diào)用。更多關(guān)于這個 后。

請記住,此處的重點是擁有一個公開 API 的應(yīng)用程序,用戶可以使用該 API 發(fā)送可由 每個請求的狀態(tài)機。??REST??

以下狀態(tài)機配置模擬了我們在 狀態(tài)圖。各種操作更新狀態(tài)機以跟蹤進入各種狀態(tài)的條目數(shù)以及如何 很多時候,和 的內(nèi)部轉(zhuǎn)換被調(diào)用以及是否已執(zhí)行:??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();}

暫時不要關(guān)注 OR,因為我們在本節(jié)后面會解釋這些內(nèi)容。??stateMachineTarget????@Scope??

我們設(shè)置了一個默認為 本地主機和默認端口。我們與實現(xiàn)一起使用。最后,我們創(chuàng)建一個使用先前 創(chuàng)建了豆子。??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);}

我們創(chuàng)建一個名為 . 狀態(tài)機實例化是一個相對 操作成本高昂,因此最好嘗試池化實例 為每個請求實例化一個新實例。為此,我們首先 創(chuàng)建包裝和池化 它的最大大小為三個。然后使用作用域代理此內(nèi)容。實際上,這意味著 每個請求都從 豆廠。稍后,我們將展示如何使用這些實例。 下面的清單顯示了我們?nèi)绾蝿?chuàng)建和設(shè)置目標源:??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;}

下面的清單顯示了我們設(shè)置最大大小并設(shè)置目標 Bean 名稱:

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

現(xiàn)在我們可以進入實際演示了。您需要在 上運行一個 Redis 服務(wù)器 具有默認設(shè)置的本地主機。然后,您需要運行基于啟動的示例 通過運行以下命令的應(yīng)用程序:

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

在瀏覽器中,您會看到如下所示的內(nèi)容:

在此 UI 中,可以使用三個用戶:、 和 。 單擊按鈕將顯示當前狀態(tài)和擴展狀態(tài)。啟用 單擊按鈕之前的單選按鈕會為此發(fā)送特定事件 用戶。這種安排允許您使用 UI。??joe????bob????dave??

在我們的 中,我們自動連線和 . 是作用域,所以你 為每個請求獲取一個新實例,而 單例豆。 下面列出了自動連線和:??StateMachineController????StateMachine????StateMachinePersister????StateMachine????request????StateMachinePersist????StateMachine????StateMachinePersist??

@Autowiredprivate StateMachine stateMachine;@Autowiredprivate StateMachinePersister stateMachinePersister;

在下面的清單中,與 UI 一起使用以執(zhí)行與 實際 API 可能會執(zhí)行以下操作:??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 內(nèi)容。??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());}

在下面的清單中,將事件發(fā)送到 并保留 其狀態(tài)通過使用:??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);}

以下清單顯示了用于還原狀態(tài)機的 對于特定用戶:??resetStateMachineFromStore??

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

與通常使用 UI 發(fā)送事件一樣,也可以使用調(diào)用來執(zhí)行相同的操作, 如以下 curl 命令所示:??REST??

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

此時,您應(yīng)該在 Redis 中具有鍵為 、 的內(nèi)容,如以下示例所示:??testprefix:joe??

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

接下來的三個圖像顯示 的狀態(tài)何時從 更改為 以及何時執(zhí)行操作。??joe????HOME????ITEMS????ADD??

下圖顯示了正在發(fā)送的事件:??ADD??

現(xiàn)在你還在狀態(tài),內(nèi)部過渡導(dǎo)致 要增加到的擴展狀態(tài)變量,如下圖所示:??ITEMS????COUNT????1??

現(xiàn)在,您可以運行以下 rest 調(diào)用幾次(或通過 UI 進行),然后 查看每次調(diào)用時變量增加:??curl????COUNT??

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

下圖顯示了這些操作的結(jié)果:

標簽: 狀態(tài)變量 應(yīng)用程序

上一篇:天天快看:Spring Statemachine狀態(tài)機的概念(五)
下一篇:生產(chǎn)環(huán)境 Redis 優(yōu)化記錄