Adsense

2018年1月13日土曜日

Spring Statemachineを使ってドラクエ11のポーカーを自動プレイするプログラムを作ってロイヤルストレートスライムを出すまでの道のり 第2回

Spring Statemachineを少し触ってみます。
バージョンはSpring Boot 1.5.9 + Spring Statemachine 1.2.7 を使います。

pom.xmlの設定

Spring Initializrでプロジェクトを作成しますが、DependenciesにSpring Statemachineはないので、自分でpom.xmlに依存関係を追加します。


        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>1.2.7.RELEASE</version>
        </dependency> 

        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-test</artifactId>
            <version>1.2.7.RELEASE</version>
            <scope>test</scope>
        </dependency>


spring-statemachine-coreだけで使えますが、テスト用のspring-statemachine-testも追加しておきます。

状態の定義

状態の定義にはenumが使えます。
以下のように状態のenumを作成します。

public enum States {
    INITIAL_STATE, READY_STATE, RUN_STATE, FINAL_STATE
}


イベントの定義

Spring Statemachineでは、状態遷移マシンがイベントを受け取ることで状態遷移(transition)を発生させます。
イベントの定義も同様にenumで作成できます。

public enum Events {
    READY_EVENT, RUN_EVENT, STOP_EVENT, FINAL_EVENT
}


Configクラス

Enumで状態とイベントを定義した場合は、EnumStateMachineConfigurerAdapterクラスを継承してConfigクラスを作成するのが一般的です。
クラスには、Spring Statemachineを有効化するための@EnableStateMachineアノテーションも付与します。

@Configuration
@EnableStateMachine
public class StateMachineConfig
    extends EnumStateMachineConfigurerAdapter<States, Events> {

Configクラス内では以下のようにconfigureメソッドをオーバーライドしてenumで定義した各状態を登録します。

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        // 状態を定義
        states.withStates()
            .initial(INITIAL_STATE) // 初期状態
            .state(READY_STATE)
            .state(RUN_STATE)
            .end(FINAL_STATE); // 終了状態
    }

同様にconfigureメソッドをオーバーライドして状態遷移を定義します。

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(INITIAL_STATE)
                .target(READY_STATE)
                .event(READY_EVENT)
                .action(c -> System.out.println("INITIAL to READY"))
                .and()
            .withExternal()
                .source(READY_STATE)
                .target(RUN_STATE)
                .event(RUN_EVENT)
                .action(c -> System.out.println("READY to RUN"))
                .and()
            .withExternal()
                .source(RUN_STATE)
                .target(READY_STATE)
                .event(READY_EVENT)
                .action(c -> System.out.println("RUN to READY"))
                .and()
            .withExternal()
                .source(RUN_STATE)
                .target(FINAL_STATE)
                .event(FINAL_EVENT)
                .action(c -> System.out.println("RUN to FINAL"))
                .and();
    }

withExternal()メソッドはExternal Transitionを定義するメソッドです。
External Transitionは状態から状態へ移るための遷移で、一般的に状態遷移と聞いて思い浮かべるものです。他に、状態の変更を伴わないInternal Transitionなどもあります。

withExternal()の後はsourceメソッドで遷移元の状態を、targetメソッドで遷移先の状態を指定します。

また、eventメソッドでこの遷移を発火させるイベントを指定します。

最後にactionメソッドでこの遷移時のアクションを指定します。
actionメソッドには Actionインターフェースを実装したクラスのオブジェクトを指定しますが、簡単な動作であれば上記のようにラムダを使って書くこともできます。
また、Actionは遷移だけでなく、状態に対しても設定できます(状態に入ったとき、状態中、状態を出るときのAction)。これについては次回以降説明します。

Configクラスの全体を下記に載せます。

@Configuration
@EnableStateMachine
public class StateMachineConfig
        extends EnumStateMachineConfigurerAdapter<States, Events> {
    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        // 状態を定義
        states.withStates()
            .initial(INITIAL_STATE) // 初期状態
            .state(READY_STATE)
            .state(RUN_STATE)
            .end(FINAL_STATE); // 終了状態
    }
  
    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(INITIAL_STATE)
                .target(READY_STATE)
                .event(READY_EVENT)
                .action(c -> System.out.println("INITIAL to READY"))
                .and()
            .withExternal()
                .source(READY_STATE)
                .target(RUN_STATE)
                .event(RUN_EVENT)
                .action(c -> System.out.println("READY to RUN"))
                .and()
            .withExternal()
                .source(RUN_STATE)
                .target(READY_STATE)
                .event(READY_EVENT)
                .action(c -> System.out.println("RUN to READY"))
                .and()
            .withExternal()
                .source(RUN_STATE)
                .target(FINAL_STATE)
                .event(FINAL_EVENT)
                .action(c -> System.out.println("RUN to FINAL"))
                .and();
    }
}

起動クラス

最後に起動クラスでStateMachineを操作するコードを書いてみます。

@SpringBootApplication
public class SpringstatemachineDemoApplication implements CommandLineRunner { 

    @Autowired 
    StateMachine<States, Events> stateMachine; 

    public static void main(String[] args) { 
        SpringApplication.run(SpringstatemachineDemoApplication.class, args); 
    } 

    @Override 
    public void run(String... args) throws Exception { 
        stateMachine.start(); 
    
        stateMachine.sendEvent(READY_EVENT); 
        stateMachine.sendEvent(RUN_EVENT); 
        stateMachine.sendEvent(READY_EVENT); 
        stateMachine.sendEvent(RUN_EVENT); 
        stateMachine.sendEvent(FINAL_EVENT); 
    }
}

stateMachine.start() で状態遷移マシンを起動します。(設定で自動的に起動することも可能)

その後はstateMachine.sendEventメソッドを使ってイベントを投げることで状態遷移を発生させます。
なお、Configクラスで遷移を設定していない状態とイベントの組み合わせでは、単純にイベントが無視されます。(例: INITIAL_STATEでRUN_EVENTを投げても無視される)

では、実際に動かしてみます。
標準出力に下記のように期待通りの ログが出力されました。

INITIAL to READY
READY to RUN
RUN to READY
READY to RUN
RUN to FINAL

今日はここまで。

0 件のコメント:

コメントを投稿