CLOVER🍀

That was when it all began.

Spring Statemachineのアクションを詊す

これは、なにをしたくお曞いたもの

以前に、Spring Statemachineをずりあえず動かしおみたした。

Spring Statemachineを試してみる - CLOVER🍀

今回は、「アクション」ずいうものを扱っおみたいず思いたす。

Spring Statemachineにおけるアクション

アクションがどういうものなのかは、Spring Statemachineの甚語集を確認しおみたしょう。

「アクション」は、遷移の最䞭にトリガヌされ、実行されるものみたいです。

Action

A action is a behavior run during the triggering of the transition.

Spring Statemachine / Appendices / Appendix B: State Machine Concepts / Glossary

クラッシュコヌスでのアクションの説明を芋おみたしょう。

アクションは、ステヌトマシンの状態倉化をナヌザヌのコヌドに結び぀けるものずいうこずになっおいたす。ステヌトマシンは、ステヌトマシンに
おける様々な倉化やステップステヌトの開始、終了など、状態遷移においお、アクションを実行できたす。

Actions really glue state machine state changes to a user’s own code. A state machine can run an action on various changes and on the steps in a state machine (such as entering or exiting a state) or doing a state transition.

Spring Statemachine / Appendices / Appendix B: State Machine Concepts / A State Machine Crash Course / Actions

たた、アクションはステヌトのコンテキストにアクセスできるため、アクションを構成するコヌド内でステヌトマシンずやり取りするこずが
できたす。

Actions usually have access to a state context, which gives running code a choice to interact with a state machine in various ways. State context exposes a whole state machine so that a user can access extended state variables, event headers (if a transition is based on an event), or an actual transition (where it is possible to see more detailed about where this state change is coming from and where it is going).

ずいうわけで、アクションずはステヌトマシンの状態倉化などに合わせお、なにか凊理を実行するもののようですね。

こう曞くず、近いものずしおリスナヌがあった気がしたす。リスナヌは、ステヌトマシンの開始、終了なども含めお、ステヌトマシンに
起こったむベントに察しお玐付けるものです。アクションよりも、もうちょっず範囲が広そうですね。

Spring Statemachine / Using Spring Statemachine / Listening to State Machine Events

䞀方で、こちらを芋るずアクションに察しおもリスナヌず䌌たようなこずが曞かれおいたりもしたすが 。

You can run actions in various places in a state machine and its states lifecycle

Spring Statemachine / Using Spring Statemachine / Using Actions

この箇所では、ステヌトの開始、終了にSpringのBeanずしお定矩したアクションを玐付けおいる䟋になっおいたす。

@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
        throws Exception {
    states
        .withStates()
            .initial(States.SI)
            .state(States.S1, action1(), action2())
            .state(States.S2, action1(), action2())
            .state(States.S3, action1(), action3());
}

Bean定矩。

@Bean
public Action<States, Events> action1() {
    return new Action<States, Events>() {

        @Override
        public void execute(StateContext<States, Events> context) {
        }
    };
}

@Bean
public BaseAction action2() {
    return new BaseAction();
}

@Bean
public SpelAction action3() {
    ExpressionParser parser = new SpelExpressionParser();
    return new SpelAction(
            parser.parseExpression(
                    "stateMachine.sendEvent(T(org.springframework.statemachine.docs.Events).E1)"));
}

public class BaseAction implements Action<States, Events> {

    @Override
    public void execute(StateContext<States, Events> context) {
    }
}

public class SpelAction extends SpelExpressionAction<States, Events> {

    public SpelAction(Expression expression) {
        super(expression);
    }
}

ずいうわけで、SpringのBeanをアクションずしお䜿えるようです。内容は、SpELでも曞けるようですが。蚘述できるこずには、自由床が
ありそうですね。

アクションの構成に関する蚘述を芋おみたしょう。

Spring Statemachine / Using Spring Statemachine / Statemachine Configuration / Configuring Actions

アクションは、遷移Transitionずステヌトに察しお定矩できるず曞かれおいたす。

You can define actions to be executed with transitions and states.

こちらは、遷移に察する定矩の䟋になっおいたす。

   @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(States.S1)
                .target(States.S2)
                .event(Events.E1)
                .action(action());
    }

こちらはステヌトに察する定矩の䟋。

   @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
            .withStates()
                .initial(States.S1, action())
                .state(States.S1, action(), null)
                .state(States.S2, null, action())
                .state(States.S2, action())
                .state(States.S3, action(), action());
    }

StateMachineTransitionConfigurerを䜿っお定矩するか、StateMachineStateConfigurerを䜿っお定矩するか、ずいうずころですね。
実際にアクションを定矩するむンタヌフェヌスはこちらのようですが。

ここたで芋おいるず、アクションずいうのは倧きく以䞋の2぀のものに分けられそうですね。

  • 遷移に察するアクション 
 ゜ヌスずなるステヌトから、タヌゲットずなるステヌトぞの遷移時に実行される
  • ステヌトに察するアクション 
 あるステヌトの開始、終了時に実行される

ドキュメントをもう少し远っおみたしょう。

遷移に察するアクション

遷移に察しおアクションを定矩した堎合は、状態倉化をトリガヌずしお発生した遷移の結果ずしお、垞にアクションが実行されたす。

An action is always run as a result of a transition that originates from a trigger.

遷移に玐付けたアクションで゚ラヌが発生した堎合は、actionメ゜ッドの第2匕数に枡したアクションでハンドリングできるようです。

Spring Statemachine / Using Spring Statemachine / Statemachine Configuration / Configuring ActionsTransition Action Error Handling

TransitionConfigurer (Spring State Machine 3.2.0 API)

こちらの䟋ですね。

   @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(States.S1)
                .target(States.S2)
                .event(Events.E1)
                .action(action(), errorAction());
    }

゚ラヌハンドリング甚のアクションには、発生した䟋倖が含たれたStateContextが枡されるようです。

なお、Actions#errorCallingActionを䜿甚するこずで、通垞のアクションず゚ラヌ甚のアクションを合成しおひず぀のアクションずしお
蚭定するこずもできるようです。

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
        throws Exception {
    transitions
        .withExternal()
            .source(States.S1)
            .target(States.S2)
            .event(Events.E1)
            .action(Actions.errorCallingAction(action(), errorAction()));
}

ステヌトに察するアクション

ステヌトに察するアクションは、こちらに蚘述がありたす。

アクションの定矩は、こちらで行いたす。

StateConfigurer (Spring State Machine 3.2.0 API)

ステヌトに察するアクションは、ステヌトの開始ず終了に関連付けられたアクションがあり、それぞれ異なる方法で実行されたす。
これは、ステヌトの開始で実行され、特定のアクションが完了する前にステヌトが終了するずそのアクションをキャンセルする可胜性が
あるからです。

State actions are run differently compared to entry and exit actions, because execution happens after state has been entered and can be cancelled if state exit happens before a particular action has been completed.

アクションはReactorのスケゞュヌラヌを䜿っおサブスクラむブするこずで実行されたす。このため、スレッドの割り蟌みをハンドリングする
必芁があるこずを意味しおいたす。

State actions are executed using normal reactive flow by subscribing with a Reactor’s default parallel scheduler. This means that, whatever you do in your action, you need to be able to catch InterruptedException or, more generally, periodically check whether Thread is interrupted.

アクションに察する実行ポリシヌも指定できるようです。状態が完了した時、たたはタむムアりトした時にキャンセルするかどうか、ですね。

StateDoActionPolicy (Spring State Machine 3.2.0 API)

アクションの゚ラヌハンドリングに察する蚘述は、こちら。

Spring Statemachine / Using Spring Statemachine / Statemachine Configuration / Configuring Actions / State Action Error Handling

こちらは、3皮類のタむミングすべおにアクションおよび゚ラヌアクションを玐付けた䟋です。

   @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
            .withStates()
                .initial(States.S1)
                .stateEntry(States.S2, action(), errorAction())
                .stateDo(States.S2, action(), errorAction())
                .stateExit(States.S2, action(), errorAction())
                .state(States.S3);
    }

゚ラヌアクションに枡されるStateContextには、䟋倖情報が含たれるこずは遷移に察するアクションず同じですね。

ずころで、Doのタむミングがドキュメントに出おきおいない気がしたすね 。あずで確認しおみたしょう 。

ステヌトに察するアクションは玐付けを行うバリ゚ヌションが倚く、コレクションで耇数のアクションを玐付けるこずができたす。

https://github.com/spring-projects/spring-statemachine/blob/v3.2.0/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java#L173-L282

ずりあえず、ドキュメントを眺めるのはこんなずころでしょうか。次は、実際にSpring Statemachineでアクションを動かしおみたしょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-125-generic", arch: "amd64", family: "unix"

プロゞェクトを䜜成する

Spring Bootプロゞェクトを䜜成したす。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.6.7 \
  -d javaVersion=17 \
  -d name=statemachine-action \
  -d groupId=org.littlewings \
  -d artifactId=statemachine-action \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.statemachine \
  -d baseDir=statemachine-action | tar zxvf -

少し叀めですが、ドキュメントに習っおSpring Bootのバヌゞョンは2.6.7にしおおきたす。

Spring Statemachine / Getting started / Using Maven

プロゞェクト内ぞ移動。

$ cd statemachine-action

自動生成された゜ヌスコヌドは削陀しおおきたす。

$ rm src/main/java/org/littlewings/spring/statemachine/StatemachineActionApplication.java src/test/java/org/littlewings/spring/statemachine/StatemachineActionApplicationTests.java

Maven䟝存関係など。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter</artifactId>
                </dependency>

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

spring-boot-starterをspring-statemachine-starterに倉曎したす。

 <dependencies>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

では、゜ヌスコヌドを䜜成しおいきたす。

ステヌトを定矩したenum。

src/main/java/org/littlewings/spring/statemachine/States.java

package org.littlewings.spring.statemachine;

public enum States {
    INITIAL_STATE,
    STATE1,
    STATE2,
    STATE3,
    STATE4,
    STATE5,
    END_STATE
}

ちょっず倚めですが、アクションを玐付けるバリ゚ヌションをいろいろ詊そうずした結果です 。

むベントを定矩したenum。

src/main/java/org/littlewings/spring/statemachine/Events.java

package org.littlewings.spring.statemachine;

public enum Events {
    EVENT1,
    EVENT2,
    EVENT3,
    EVENT4,
    EVENT5,
    EVENT6
}

ステヌトマシンの定矩。ちょっず長いですが、これは埌で説明したす。

src/main/java/org/littlewings/spring/statemachine/StateMachineConfig.java

package org.littlewings.spring.statemachine;

import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config)
            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .machineId("my-statemachine");
    }

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.INITIAL_STATE)
                //.initial(States.INITIAL_STATE, stateAction())

                .state(States.STATE1)
                //.state(States.STATE1, stateAction())

                .state(States.STATE2)
                //.state(States.STATE2, stateAction(), stateAction())

                .state(States.STATE3)
                //.stateEntry(States.STATE3, stateEntryAction())
                //.stateDo(States.STATE3, stateDoAction())  // synonym state(state, action)
                //.stateExit(States.STATE3, stateExitAction())

                .state(States.STATE4)
                //.state(States.STATE4, stateAction(), stateAction())
                //.stateEntry(States.STATE4, stateActionThrowException(), stateActionHandleError())
                //.stateDo(States.STATE4, stateActionThrowException(), stateActionHandleError())
                //.stateExit(States.STATE4, stateActionThrowException(), stateActionHandleError())

                .state(States.STATE5)
                //.state(States.STATE5, List.of(stateAction(), stateAction()), List.of(stateAction(), stateAction()))  // collection

                .end(States.END_STATE);  // endにはactionなし
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.INITIAL_STATE)
                .target(States.STATE1)
                .event(Events.EVENT1)
                //.action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE1)
                .target(States.STATE2)
                .event(Events.EVENT2)
                //.action(transitionAction())
                //.action(transitionActionThrowException(), transitionActionHandleError())

                .and()
                .withExternal()
                .source(States.STATE2)
                .target(States.STATE3)
                .event(Events.EVENT3)
                //.action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE3)
                .target(States.STATE4)
                .event(Events.EVENT4)
                //.action(transitionAction())
                //.action(transitionActionThrowException(), transitionActionHandleError())

                .and()
                .withExternal()
                .source(States.STATE4)
                .target(States.STATE5)
                .event(Events.EVENT5)
                //.action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE5)
                .target(States.END_STATE)
                .event(Events.EVENT6);
                //.action(transitionAction());
    }

    @Bean
    public Action<States, Events> stateAction() {
        return stateContext ->
                System.out.printf(
                        "state action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateEntryAction() {
        return stateContext ->
                System.out.printf(
                        "state entry action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateDoAction() {
        return stateContext ->
                System.out.printf(
                        "state do action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateExitAction() {
        return stateContext ->
                System.out.printf(
                        "state exit action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateActionThrowException() {
        return stateContext -> {
            System.out.printf(
                    "state action throw exception, stage = %s, state = %s, event = %s%n",
                    stateContext.getStage(),
                    stateContext.getTarget().getId(),
                    stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
            );

            throw new RuntimeException("state action error!!");
        };
    }

    @Bean
    public Action<States, Events> stateActionHandleError() {
        return stateContext ->
                System.out.printf(
                        "state action handling exception, stage = %s, state = %s, event = %s, exception message = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]",
                        stateContext.getException().getMessage()
                );
    }

    @Bean
    public Action<States, Events> transitionAction() {
        return stateContext ->
                System.out.printf(
                        "transition action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> transitionActionThrowException() {
        return stateContext -> {
            System.out.printf(
                    "transition action throw exception, stage = %s, state = %s, event = %s%n",
                    stateContext.getStage(),
                    stateContext.getTarget().getId(),
                    stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
            );

            throw new RuntimeException("transition action error!!");
        };
    }

    @Bean
    public Action<States, Events> transitionActionHandleError() {
        return stateContext ->
                System.out.printf(
                        "transition action handling exception, stage = %s, state = %s, event = %s, exception message = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]",
                        stateContext.getException().getMessage()
                );
    }
}

ステヌトマシンを䜿うクラス。

src/main/java/org/littlewings/spring/statemachine/Runner.java

package org.littlewings.spring.statemachine;

import java.util.concurrent.TimeUnit;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class Runner implements ApplicationRunner {
    StateMachine<States, Events> stateMachine;

    public Runner(StateMachine<States, Events> stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT1).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT2).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT3).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT4).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT5).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT6).build()))
                .blockFirst();
    }
}

mainメ゜ッドを持ったクラス。

src/main/java/org/littlewings/spring/statemachine/App.java

package org.littlewings.spring.statemachine;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }
}

今回のメむンはもちろんステヌトマシンに定矩したステヌト、遷移に玐付けたアクションの確認なのですが。
最初に茉せたものがいろいろずごちゃごちゃしおいたので、たずはステヌトず遷移のベヌスの定矩を茉せたしょう。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config)
            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .machineId("my-statemachine");
    }

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.INITIAL_STATE)
                .state(States.STATE1)
                .state(States.STATE2)
                .state(States.STATE3)
                .state(States.STATE4)
                .state(States.STATE5)
                .end(States.END_STATE);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.INITIAL_STATE)
                .target(States.STATE1)
                .event(Events.EVENT1)

                .and()
                .withExternal()
                .source(States.STATE1)
                .target(States.STATE2)
                .event(Events.EVENT2)

                .and()
                .withExternal()
                .source(States.STATE2)
                .target(States.STATE3)
                .event(Events.EVENT3)

                .and()
                .withExternal()
                .source(States.STATE3)
                .target(States.STATE4)
                .event(Events.EVENT4)

                .and()
                .withExternal()
                .source(States.STATE4)
                .target(States.STATE5)
                .event(Events.EVENT5)

                .and()
                .withExternal()
                .source(States.STATE5)
                .target(States.END_STATE)
                .event(Events.EVENT6);
    }

    〜省略〜
}

ただいずれにもアクションは玐付けおいたせん。

この状態で、アプリケヌションを実行。

$ mvn spring-boot:run

たあ、特になにも衚瀺されないのですが。

2022-09-02 00:48:08.062  INFO 25583 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration' of type [org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration$$EnhancerBySpringCGLIB$$95adde42] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-09-02 00:48:08.450  INFO 25583 --- [           main] org.littlewings.spring.statemachine.App  : Started App in 1.26 seconds (JVM running for 1.549)
2022-09-02 00:48:08.498  INFO 25583 --- [ionShutdownHook] o.s.s.support.LifecycleObjectSupport     : destroy called

ここから、ステヌトや遷移にアクションを玐付けおいきたす。

ステヌトにアクションを玐付ける

たずは、ステヌトにアクションを玐付けおみたす。

以䞋のようにアクションを玐付けたした。

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.INITIAL_STATE, stateAction())

                .state(States.STATE1, stateAction())

                .state(States.STATE2, stateAction(), stateAction())

                .stateEntry(States.STATE3, stateEntryAction())
                .stateDo(States.STATE3, stateDoAction())  // synonym state(state, action)
                .stateExit(States.STATE3, stateExitAction())

                .state(States.STATE4, stateAction(), stateAction())

                .state(States.STATE5, List.of(stateAction(), stateAction()), List.of(stateAction(), stateAction()))  // collection

                .end(States.END_STATE);  // endにはactionなし
    }

ステヌトに察しおひず぀アクションを玐付けた堎合は、ステヌトの開始時ぞの玐付け

                .state(States.STATE1, stateAction())

2぀の堎合は開始ず終了、

                .state(States.STATE2, stateAction(), stateAction())

開始ず終了のタむミング別の玐付DoはEntryの別名のようです、

                .stateEntry(States.STATE3, stateEntryAction())
                .stateDo(States.STATE3, stateDoAction())  // synonym state(state, action)
                .stateExit(States.STATE3, stateExitAction())

開始ず終了のタむミングで、耇数のアクションを玐付ずいうバリ゚ヌションです。

                .state(States.STATE5, List.of(stateAction(), stateAction()), List.of(stateAction(), stateAction()))  // collection

最埌のステヌトには、アクションは玐付けられないみたいです。

                .end(States.END_STATE);  // endにはactionなし

玐付けたアクションの定矩はこちらで、ステヌゞやステヌト、発生したむベントの情報を暙準出力に曞き出しおいたす。

    @Bean
    public Action<States, Events> stateAction() {
        return stateContext ->
                System.out.printf(
                        "state action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateEntryAction() {
        return stateContext ->
                System.out.printf(
                        "state entry action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateDoAction() {
        return stateContext ->
                System.out.printf(
                        "state do action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

    @Bean
    public Action<States, Events> stateExitAction() {
        return stateContext ->
                System.out.printf(
                        "state exit action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

では、動かしおみたす。

$ mvn spring-boot:run

System.out.printlnしおいた郚分を抜き出すず、こういう出力が埗られたした。

state action, stage = TRANSITION, state = INITIAL_STATE, event = [none]
state action, stage = STATE_ENTRY, state = STATE1, event = EVENT1
state action, stage = STATE_ENTRY, state = STATE2, event = EVENT2
state action, stage = STATE_EXIT, state = STATE3, event = EVENT3
state entry action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state do action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state exit action, stage = STATE_EXIT, state = STATE4, event = EVENT4
state action, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action, stage = STATE_EXIT, state = STATE5, event = EVENT5
state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6

゜ヌスコヌドず玐付けるず、こんな感じでしょうか。

// .initial(States.INITIAL_STATE, stateAction())
state action, stage = TRANSITION, state = INITIAL_STATE, event = [none]

// .state(States.STATE1, stateAction())
state action, stage = STATE_ENTRY, state = STATE1, event = EVENT1

.state(States.STATE2, stateAction(), stateAction())
state action, stage = STATE_ENTRY, state = STATE2, event = EVENT2
state action, stage = STATE_EXIT, state = STATE3, event = EVENT3

// .stateEntry(States.STATE3, stateEntryAction())
// .stateDo(States.STATE3, stateDoAction())  // synonym state(state, action)
// .stateExit(States.STATE3, stateExitAction())
state entry action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state do action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state exit action, stage = STATE_EXIT, state = STATE4, event = EVENT4

// .state(States.STATE4, stateAction(), stateAction())
state action, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action, stage = STATE_EXIT, state = STATE5, event = EVENT5

// .state(States.STATE5, List.of(stateAction(), stateAction()), List.of(stateAction(), stateAction()))
state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6

endにはアクションは玐付けられたせんでしたね。

次に、䞀郚のアクションの玐付けを倉曎しおみたす。

        states
                .withStates()
                .initial(States.INITIAL_STATE, stateAction())

                .state(States.STATE1, stateAction())

                .state(States.STATE2, stateAction(), stateAction())

                .stateEntry(States.STATE3, stateEntryAction())
                .stateDo(States.STATE3, stateDoAction())  // synonym state(state, action)
                .stateExit(States.STATE3, stateExitAction())

                .stateEntry(States.STATE4, stateActionThrowException(), stateActionHandleError())
                .stateDo(States.STATE4, stateActionThrowException(), stateActionHandleError())
                .stateExit(States.STATE4, stateActionThrowException(), stateActionHandleError())

                .state(States.STATE5, List.of(stateAction(), stateAction()), List.of(stateAction(), stateAction()))  // collection

                .end(States.END_STATE);  // endにはactionなし

倉わったのは、STATE4ですね。

こちらには、䟋倖を投げるアクションず、アクションから投げられた䟋倖をハンドリングするアクションを玐付けおいたす。

アクションの定矩は、こちら。

    @Bean
    public Action<States, Events> stateActionThrowException() {
        return stateContext -> {
            System.out.printf(
                    "state action throw exception, stage = %s, state = %s, event = %s%n",
                    stateContext.getStage(),
                    stateContext.getTarget().getId(),
                    stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
            );

            throw new RuntimeException("state action error!!");
        };
    }

    @Bean
    public Action<States, Events> stateActionHandleError() {
        return stateContext ->
                System.out.printf(
                        "state action handling exception, stage = %s, state = %s, event = %s, exception message = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]",
                        stateContext.getException().getMessage()
                );
    }

実行。

$ mvn spring-boot:run

スタックトレヌスを出力し぀぀も、すべおのステヌトを経お終了したした。

state action, stage = TRANSITION, state = INITIAL_STATE, event = [none]
2022-09-02 01:05:35.735  INFO 26623 --- [           main] org.littlewings.spring.statemachine.App  : Started App in 1.017 seconds (JVM running for 1.256)
state action, stage = STATE_ENTRY, state = STATE1, event = EVENT1
state action, stage = STATE_ENTRY, state = STATE2, event = EVENT2
state action, stage = STATE_EXIT, state = STATE3, event = EVENT3
state entry action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state do action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
state exit action, stage = STATE_EXIT, state = STATE4, event = EVENT4
state action throw exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action handling exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4, exception message = state action error!!
2022-09-02 01:05:35.777  WARN 26623 --- [           main] o.s.statemachine.state.ObjectState       : Entry action execution error

java.lang.RuntimeException: state action error!!
        at org.littlewings.spring.statemachine.StateMachineConfig.lambda$stateActionThrowException$4(StateMachineConfig.java:159) ~[classes/:na]
        at org.springframework.statemachine.action.Actions$2.execute(Actions.java:71) ~[spring-statemachine-core-3.2.0.jar:3.2.0]
        at org.springframework.statemachine.action.Actions.lambda$null$0(Actions.java:98) ~[spring-statemachine-core-3.2.0.jar:3.2.0]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:32) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:252) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4400) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:203) ~[reactor-core-3.4.17.jar:3.4.17]

        〜省略〜
       
        at reactor.core.publisher.Flux.subscribe(Flux.java:8455) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.Flux.blockFirst(Flux.java:2599) ~[reactor-core-3.4.17.jar:3.4.17]
        at org.littlewings.spring.statemachine.Runner.run(Runner.java:33) ~[classes/:na]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:768) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:758) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.littlewings.spring.statemachine.App.main(App.java:9) ~[classes/:na]

state action throw exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action handling exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4, exception message = state action error!!
state action throw exception, stage = STATE_EXIT, state = STATE5, event = EVENT5
state action handling exception, stage = STATE_EXIT, state = STATE5, event = EVENT5, exception message = state action error!!
2022-09-02 01:05:35.784  WARN 26623 --- [           main] o.s.statemachine.state.ObjectState       : Exit action execution error

java.lang.RuntimeException: state action error!!
        at org.littlewings.spring.statemachine.StateMachineConfig.lambda$stateActionThrowException$4(StateMachineConfig.java:159) ~[classes/:na]
        at org.springframework.statemachine.action.Actions$2.execute(Actions.java:71) ~[spring-statemachine-core-3.2.0.jar:3.2.0]
        at org.springframework.statemachine.action.Actions.lambda$null$0(Actions.java:98) ~[spring-statemachine-core-3.2.0.jar:3.2.0]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:32) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:252) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4400) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:203) ~[reactor-core-3.4.17.jar:3.4.17]

        〜省略〜
       
        at reactor.core.publisher.Flux.subscribe(Flux.java:8469) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:200) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.MonoFlatMapMany.subscribeOrReturn(MonoFlatMapMany.java:49) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.Flux.subscribe(Flux.java:8455) ~[reactor-core-3.4.17.jar:3.4.17]
        at reactor.core.publisher.Flux.blockFirst(Flux.java:2599) ~[reactor-core-3.4.17.jar:3.4.17]
        at org.littlewings.spring.statemachine.Runner.run(Runner.java:36) ~[classes/:na]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:768) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:758) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.7.jar:2.6.7]
        at org.littlewings.spring.statemachine.App.main(App.java:9) ~[classes/:na]

state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_ENTRY, state = STATE5, event = EVENT5
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6
state action, stage = STATE_EXIT, state = END_STATE, event = EVENT6

Spring StatemachineずReactorが䟋倖を拟っおいたすが、実行はそのたた続いおいたすね。

䟋倖を投げ、ハンドリングしおいるアクションず゜ヌスコヌドの察比を茉せるずこんな感じです。

// .stateEntry(States.STATE4, stateActionThrowException(), stateActionHandleError())
state action throw exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action handling exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4, exception message = state action error!!

// .stateDo(States.STATE4, stateActionThrowException(), stateActionHandleError())
state action throw exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4
state action handling exception, stage = STATE_ENTRY, state = STATE4, event = EVENT4, exception message = state action error!!

// .stateExit(States.STATE4, stateActionThrowException(), stateActionHandleError())
state action throw exception, stage = STATE_EXIT, state = STATE5, event = EVENT5
state action handling exception, stage = STATE_EXIT, state = STATE5, event = EVENT5, exception message = state action error!!

だいたい雰囲気はわかりたしたね。

ちなみに、゚ラヌハンドリング甚のアクションで䟋倖は扱っおいるはずなのに、どうしおSpring StatemachineずReactorでスタックトレヌスが
珟れるかずいうず、゚ラヌ甚のアクションを呌び出した埌で䟋倖を再スロヌしおいるからのようです 。

https://github.com/spring-projects/spring-statemachine/blob/v3.2.0/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Actions.java#L82

では、1床ステヌトの定矩からアクションの玐付けを倖したす。

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.INITIAL_STATE)
                .state(States.STATE1)
                .state(States.STATE2)
                .state(States.STATE3)
                .state(States.STATE4)
                .state(States.STATE5)
                .end(States.END_STATE);
    }

遷移にアクションを玐付ける

次は、遷移に察しおアクションを玐付けおみたす。

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.INITIAL_STATE)
                .target(States.STATE1)
                .event(Events.EVENT1)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE1)
                .target(States.STATE2)
                .event(Events.EVENT2)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE2)
                .target(States.STATE3)
                .event(Events.EVENT3)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE3)
                .target(States.STATE4)
                .event(Events.EVENT4)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE4)
                .target(States.STATE5)
                .event(Events.EVENT5)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE5)
                .target(States.END_STATE)
                .event(Events.EVENT6)//;
                .action(transitionAction());
    }

今回は、すべおの遷移に察しお同じアクションを玐付けおいたす。

アクションの定矩は、こちら。

    @Bean
    public Action<States, Events> transitionAction() {
        return stateContext ->
                System.out.printf(
                        "transition action, stage = %s, state = %s, event = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
                );
    }

実行しおみたす。

$ mvn spring-boot:run

結果は、ずおもシンプルです。

transition action, stage = TRANSITION, state = STATE1, event = EVENT1
transition action, stage = TRANSITION, state = STATE2, event = EVENT2
transition action, stage = TRANSITION, state = STATE3, event = EVENT3
transition action, stage = TRANSITION, state = STATE4, event = EVENT4
transition action, stage = TRANSITION, state = STATE5, event = EVENT5
transition action, stage = TRANSITION, state = END_STATE, event = EVENT6

では、ここで遷移の2぀に䟋倖を扱うアクションを远加しおみたしょう。

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.INITIAL_STATE)
                .target(States.STATE1)
                .event(Events.EVENT1)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE1)
                .target(States.STATE2)
                .event(Events.EVENT2)
                .action(transitionActionThrowException(), transitionActionHandleError())

                .and()
                .withExternal()
                .source(States.STATE2)
                .target(States.STATE3)
                .event(Events.EVENT3)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE3)
                .target(States.STATE4)
                .event(Events.EVENT4)
                .action(transitionActionThrowException(), transitionActionHandleError())

                .and()
                .withExternal()
                .source(States.STATE4)
                .target(States.STATE5)
                .event(Events.EVENT5)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE5)
                .target(States.END_STATE)
                .event(Events.EVENT6)//;
                .action(transitionAction());
    }

䟋倖を扱うアクションは、こちら。内容自䜓はステヌトの時ず同じです。

    @Bean
    public Action<States, Events> transitionActionThrowException() {
        return stateContext -> {
            System.out.printf(
                    "transition action throw exception, stage = %s, state = %s, event = %s%n",
                    stateContext.getStage(),
                    stateContext.getTarget().getId(),
                    stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]"
            );

            throw new RuntimeException("transition action error!!");
        };
    }

    @Bean
    public Action<States, Events> transitionActionHandleError() {
        return stateContext ->
                System.out.printf(
                        "transition action handling exception, stage = %s, state = %s, event = %s, exception message = %s%n",
                        stateContext.getStage(),
                        stateContext.getTarget().getId(),
                        stateContext.getMessage() != null ? stateContext.getMessage().getPayload() : "[none]",
                        stateContext.getException().getMessage()
                );
    }

実行しおみたす。

$ mvn spring-boot:run

こちらは、ステヌトの時ずは異なった結果になりたした。

transition action, stage = TRANSITION, state = STATE1, event = EVENT1
transition action throw exception, stage = TRANSITION, state = STATE2, event = EVENT2
transition action handling exception, stage = TRANSITION, state = STATE2, event = EVENT2, exception message = transition action error!!

アクションで発生した䟋倖をハンドリングした時点で、ステヌトマシンが終了しおしたいたした。

こちらの凊理自䜓はすべお実行されおいるのですが、EVENT2を送信したずころでそれ以降は受け付けなくなっおいるようです。

    @Override
    public void run(ApplicationArguments args) throws Exception {
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT1).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT2).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT3).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT4).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT5).build()))
                .blockFirst();
        stateMachine
                .sendEvent(Mono.just(MessageBuilder.withPayload(Events.EVENT6).build()))
                .blockFirst();
    }

遷移に玐付けたアクションの堎合は、䟋倖をハンドリングしおも停止しおしたうんですね 。

ちなみに、アクションが䟋倖をハンドリングした埌も䟋倖を再スロヌするのは、やっぱり以䞋の郚分です。

https://github.com/spring-projects/spring-statemachine/blob/v3.2.0/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Actions.java#L82

ステヌトず遷移の䞡方にアクションを玐付ける

最埌に、ステヌトず遷移の䞡方にアクションを玐付けた時の動䜜を芋おみたしょう。今回は、䟋倖は䜿いたせん。

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.INITIAL_STATE, stateAction())

                .state(States.STATE1, stateAction())

                .state(States.STATE2, stateAction(), stateAction())

                .stateEntry(States.STATE3, stateEntryAction())
                .stateExit(States.STATE3, stateExitAction())

                .state(States.STATE4, stateAction(), stateAction())

                .state(States.STATE5)

                .end(States.END_STATE);  // endにはactionなし
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.INITIAL_STATE)
                .target(States.STATE1)
                .event(Events.EVENT1)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE1)
                .target(States.STATE2)
                .event(Events.EVENT2)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE2)
                .target(States.STATE3)
                .event(Events.EVENT3)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE3)
                .target(States.STATE4)
                .event(Events.EVENT4)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE4)
                .target(States.STATE5)
                .event(Events.EVENT5)
                .action(transitionAction())

                .and()
                .withExternal()
                .source(States.STATE5)
                .target(States.END_STATE)
                .event(Events.EVENT6)//;
                .action(transitionAction());
    }

実行。

$ mvn spring-boot:run

結果は、こんな感じになりたした。

state action, stage = TRANSITION, state = INITIAL_STATE, event = [none]
transition action, stage = TRANSITION, state = STATE1, event = EVENT1
state action, stage = STATE_ENTRY, state = STATE1, event = EVENT1
transition action, stage = TRANSITION, state = STATE2, event = EVENT2
state action, stage = STATE_ENTRY, state = STATE2, event = EVENT2
transition action, stage = TRANSITION, state = STATE3, event = EVENT3
state action, stage = STATE_EXIT, state = STATE3, event = EVENT3
state entry action, stage = STATE_ENTRY, state = STATE3, event = EVENT3
transition action, stage = TRANSITION, state = STATE4, event = EVENT4
state exit action, stage = STATE_EXIT, state = STATE4, event = EVENT4
state action, stage = STATE_ENTRY, state = STATE4, event = EVENT4
transition action, stage = TRANSITION, state = STATE5, event = EVENT5
state action, stage = STATE_EXIT, state = STATE5, event = EVENT5
transition action, stage = TRANSITION, state = END_STATE, event = EVENT6

゜ヌスコヌドず玐付けおみたしょう。

// .initial(States.INITIAL_STATE, stateAction())
state action, stage = TRANSITION, state = INITIAL_STATE, event = [none]

// .source(States.INITIAL_STATE)
// .target(States.STATE1)
// .event(Events.EVENT1)
// .action(transitionAction())
transition action, stage = TRANSITION, state = STATE1, event = EVENT1

// .state(States.STATE1, stateAction())
state action, stage = STATE_ENTRY, state = STATE1, event = EVENT1

// .source(States.STATE1)
// .target(States.STATE2)
// .event(Events.EVENT2)
// .action(transitionAction())
transition action, stage = TRANSITION, state = STATE2, event = EVENT2

// .state(States.STATE2, stateAction(), stateAction())
state action, stage = STATE_ENTRY, state = STATE2, event = EVENT2

// .source(States.STATE2)
// .target(States.STATE3)
// .event(Events.EVENT3)
// .action(transitionAction())
transition action, stage = TRANSITION, state = STATE3, event = EVENT3

// .state(States.STATE2, stateAction(), stateAction())
state action, stage = STATE_EXIT, state = STATE3, event = EVENT3

// .stateExit(States.STATE3, stateExitAction())
state entry action, stage = STATE_ENTRY, state = STATE3, event = EVENT3

// .source(States.STATE3)
// .target(States.STATE4)
// .event(Events.EVENT4)
// .action(transitionAction())
transition action, stage = TRANSITION, state = STATE4, event = EVENT4

// .stateExit(States.STATE3, stateExitAction())
state exit action, stage = STATE_EXIT, state = STATE4, event = EVENT4

// .state(States.STATE4, stateAction(), stateAction())
state action, stage = STATE_ENTRY, state = STATE4, event = EVENT4

// .source(States.STATE4)
// .target(States.STATE5)
// .event(Events.EVENT5)
// .action(transitionAction())
transition action, stage = TRANSITION, state = STATE5, event = EVENT5

// .state(States.STATE4, stateAction(), stateAction())
state action, stage = STATE_EXIT, state = STATE5, event = EVENT5

// .source(States.STATE5)
// .target(States.END_STATE)
// .event(Events.EVENT6)//;
// .action(transitionAction());
transition action, stage = TRANSITION, state = END_STATE, event = EVENT6

こう芋るず、ステヌト開始・終了の間に遷移が挟たっおいる感じに芋えたすが、どうなんでしょうね

たあ、なんずなくアクションをステヌトおよび遷移に玐付けるず、どういう動䜜になるかはわかった気がしたす。

たずめ

Spring Statemachineのアクションを詊しおみたした。

最初は雰囲気で初めおみたのですが、ちゃんずドキュメントを芋おいるずアクションの玐付け先はステヌトず遷移の2皮類あるこずがわかり、
それぞれ考え方が埮劙に違ったので、ドキュメントを読んでおくのは倧事だなずいう気にはなりたした。

ずりあえず、雰囲気はわかったので今回はこんなずころで。