[JAVA] I tried Spring State machine

1.First of all

This time I had the opportunity to check ** spring-statement machine 1.2.12 ** depending on the purpose, so I wrote an article about what I tried at that time.

Basically, the content is a sample of the official document [7. Developing your first Spring Statemachine application](https://docs.spring.io/spring-statemachine/docs/2.0.2.RELEASE/reference/htmlsingle/#state Same as the state machine of -machine-via-builder).

However, since the sample is mainly a springboot application and config, this time the builder pattern "[13.2 State Machine via Builder]" (https://docs.spring.io/spring) that directly creates an instance of the state machine -statemachine / docs / 2.0.2.RELEASE / reference / htmlsingle / # state-machine-via-builder) ”, we will implement it as a simple console application.

2. Modify .m2 / settings.xml (if needed)

Modify settings.xml to go through PROXY to get it directly from the Maven repository on the internet.

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <proxies>
    <proxy>
      <active>true</active>
      <protocol>http</protocol>
      <host>127.0.0.1</host>
      <port>8080</port>
      <username>xxxxxx</username>
      <password>password</password>
      <nonProxyHosts>10.*</nonProxyHosts>
    </proxy>
  </proxies>
</settings>

3. Create a blank project with maven

Create a blank project with quickstart of maven.

mvn archetype:generate ^
-DinteractiveMode=false ^
-DarchetypeArtifactId=maven-archetype-quickstart ^
-DgroupId=com.example.statemachie.demo ^
-DartifactId=statemachine-demo

4. Modify pom.xml

Create a minimum pom.xml with spring-statementmachine 1.2.12 in the project created above.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.statemachie.demo</groupId>
  <artifactId>statemachine-demo</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>statemachine-demo</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- spring-statemachine -->
    <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-core</artifactId>
        <version>1.2.12.RELEASE</version>
    </dependency>
  </dependencies>

  <!-- java8 -->
  <properties>
    <java.version>1.8</java.version>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <maven.compiler.source>${java.version}</maven.compiler.source>
  </properties>

</project>

5. List of dependent libraries

I tried to get the jar file with mvn dependency: copy-dependencies -DoutputDirectory = lib. It's not a necessary task for implementation, but it was done to understand the dependencies.

    commons-logging-1.2.jar
    junit-3.8.1.jar
    spring-aop-4.3.3.RELEASE.jar
    spring-beans-4.3.3.RELEASE.jar
    spring-context-4.3.3.RELEASE.jar
    spring-core-4.3.3.RELEASE.jar
    spring-expression-4.3.3.RELEASE.jar
    spring-messaging-4.3.3.RELEASE.jar
    spring-statemachine-core-1.2.12.RELEASE.jar
    spring-tx-4.3.3.RELEASE.jar

6. File structure (src / main / java)

This is the structure of the implemented file (src / main / java).

statemachine-demo\src\main\java
└─com
    └─example
        └─statemachie
            └─demo
                    App.java
                    Events.java
                    SampleListener.java
                    States.java

7. Implementation

The contents of the implemented file.

7.1. States.java

package com.example.statemachie.demo;

public enum States {
    SI, S1, S2
}

State A simple Enum used as the state of the machine.

7.2. Events.java

package com.example.statemachie.demo;

public enum Events {
    E1, E2
}

A simple Enum used as an event to change the state of a state machine.

7.3. SampleListener.java

package com.example.statemachie.demo;

import org.springframework.messaging.Message;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

public class SampleListener extends 
    StateMachineListenerAdapter<States, Events> {

    @Override
    public void stateChanged(State<States, Events> from,
        State<States, Events> to) {
        System.out.println("State change to " + to.getId());
    }
    
    @Override
    public void eventNotAccepted(Message<Events> event) {
        System.out.println("event not accepted : " + event.getPayload());
    }
}

It is a function of spring-state machine. A listener that checks for state changes. Define a class that inherits from StateMachineListenerAdapter. The stateChanged method is called when the state is changed, and the ʻeventNotAccepted` method is called when the event is not accepted (the defined state and the event are not related, that is, an invalid event). This time I overridden these two methods to see the state transitions.

7.4. App.java

package com.example.statemachie.demo;

import java.util.EnumSet;

import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineBuilder.Builder;

public class App {
    public static void main(String[] args) {

        correctStateFlow();
        System.out.println("-----------------------");
        incorrectStateFlow();
    }
    
    private static void correctStateFlow() {
        StateMachine<States, Events> stateMachine = null;
        try {
            stateMachine = buildMachine();
        } catch (Exception e) {
            e.printStackTrace();
        }
        stateMachine.start();
        stateMachine.sendEvent(Events.E1);
        stateMachine.sendEvent(Events.E2);
        System.out.println("isComplete() before call stop : "
            + stateMachine.isComplete());
        stateMachine.stop();
        System.out.println("isComplete() after call stop : "
            + stateMachine.isComplete());
    }
    
    private static void incorrectStateFlow() {
        StateMachine<States, Events> stateMachine = null;
        try {
            stateMachine = buildMachine();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        stateMachine.start();
        stateMachine.sendEvent(Events.E2);
    }

    private static StateMachine<States, Events> buildMachine() throws Exception {
        Builder<States, Events> builder = StateMachineBuilder.builder();
        builder.configureStates()
            .withStates()
                .initial(States.SI) // set initial state
                .states(EnumSet.allOf(States.class));
        builder.configureTransitions()
            .withExternal() // add SI -(E1)-> S1
                .source(States.SI).target(States.S1).event(Events.E1)
            .and()
            .withExternal() // add S1 -(E2)-> S2
                .source(States.S1).target(States.S2).event(Events.E2);
        SampleListener sampleLister = new SampleListener();
        builder.configureConfiguration()
            .withConfiguration()
            .listener(sampleLister); // set listener
        return builder.build();
    }
}

The buildMachine () method creates an instance of ʻorg.springframework.statemachine.StateMachine. This process can be defined in Java config or XML file, but this time it is generated on demand with the builder pattern as explained in "Introduction". Once you have created an instance of StateMachine, all you have to do is start the state machine with the start () method and execute the event with the sendEvent () `method.

correctStateFlow () issues events in the correct defined order, while ʻincorrectStateFlow ()` issues events in the wrong order (issues E2 events to Sugu after the initials). I tried this to see what happens in case of an illegal event transition.

8. Execution result

It is a console log when actually moving it.

State change to SI
10 27, 2018 11:48:33 am org.springframework.statemachine.support.LifecycleObjectSupport start
information: started org.springframework.statemachine.support.DefaultStateMachineExecutor@768debd
10 27, 2018 11:48:33 am org.springframework.statemachine.support.LifecycleObjectSupport start
information: started S2 S1 SI  / SI / uuid=af9655e4-645d-4dae-9f69-fd0514cd3a9a / id=null
State change to S1
State change to S2
isComplete() before call stop : false
10 27, 2018 11:48:34 am org.springframework.statemachine.support.LifecycleObjectSupport stop
information: stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@768debd
10 27, 2018 11:48:34 am org.springframework.statemachine.support.LifecycleObjectSupport stop
information: stopped S2 S1 SI  /  / uuid=af9655e4-645d-4dae-9f69-fd0514cd3a9a / id=null
isComplete() after call stop : true
-----------------------
State change to SI
10 27, 2018 11:48:34 am org.springframework.statemachine.support.LifecycleObjectSupport start
information: started org.springframework.statemachine.support.DefaultStateMachineExecutor@387c703b
10 27, 2018 11:48:34 am org.springframework.statemachine.support.LifecycleObjectSupport start
information: started S2 S1 SI  / SI / uuid=d075b6b9-8cf5-49fb-bc8e-226bb19600f2 / id=null
event not accepted : E2

The standard output output by the event listener and the debug log when the start () and stop () methods are called are output.

When you start a state machine with start (), it first changes to the state of SI, which is the initial state defined in initial. Executing event ʻE1 while the state is SIchanges the state toS1. If event ʻE2 is executed while the state is S1, the state will be changed to S2. Even if the state is S2, ʻisComplete ()returnsfalsebecause the state machine is up. Exiting the state machine withstop () terminates the state machine, so ʻisComplete () returns true.

Due to the relationship between state and event, the state is not changed even if you try to make an undefined transition state. In the sample, the event ʻE2 was executed with the state of SI` in the initial state. The result is output by the event listener, but the event was not accepted.

9. Finally

This time I tried a sample of the official document of Spring State machine with a builder pattern. I hope you have somehow understood the outline of Spring State machine. However, it seems that further verification is required to use the Spring State machine in an actual system. Since the state is managed by the instance of StateMachine in the first place, it is unclear how to manage the state transition of long term (approval flow with user approval etc.). Documents are persisted by REDIS (28.3 Using Redis) And examples of state management in Web systems (47. Event Service) Is listed, but this is only supported by changing the scope of the bean. However, the feeling of using it was very easy, so I thought it would be useful if I could find a way to apply the Spring State machine.

Recommended Posts

I tried Spring State machine
I tried Spring.
[I tried] Spring tutorial
I tried Spring Batch
I tried using Spring + Mybatis + DbUnit
I tried GraphQL with Spring Boot
I tried Flyway with Spring Boot
I tried Oracle's machine learning OSS "Tribuo"
I tried tomcat
I tried Lazy Initialization with Spring Boot 2.2.0
I tried youtubeDataApi.
I tried refactoring ①
I tried Spring Data JDBC 1.0.0.BUILD-SNAPSHOT (-> 1.0.0.RELEASE)
I tried FizzBuzz.
I tried JHipster 5.1
I tried to link JavaFX and Spring Framework.
java state machine car
I tried running Autoware
I tried using Gson
I tried QUARKUS immediately
I tried using TestNG
I tried using Galasa
I tried node-jt400 (Programs)
I tried node-jt400 (execute)
I tried node-jt400 (Transactions)
I tried to implement file upload with Spring MVC
I tried to summarize the state transition of docker
I tried to reduce the capacity of Spring Boot
I tried to get started with Spring Data JPA
[Machine learning] I tried Object Detection with Create ML [Object detection]
I tried node-jt400 (Environment construction)
I tried DI with Ruby
I tried node-jt400 (IFS write)
Spring Boot Introductory Guide I tried [Accessing Data with JPA]
I tried node-jt400 (SQL Update)
[Swift] I tried to implement the function of the vending machine
I tried using azure cloud-init
I tried Drools (Java, InputStream)
I tried Rails beginner [Chapter 1]
I tried the Docker tutorial!
I tried using Apache Wicket
I tried the VueJS tutorial!
I tried node-jt400 (SQL query)
I tried using Java REPL
I tried source code analysis
I tried the FizzBuzz problem
I tried node-jt400 (SQL stream)
I tried node-jt400 (IFS read)
I tried putting XcodeGen + SwiftPM
I tried Rails beginner [Chapter 2]
I tried UPSERT with PostgreSQL.
I tried BIND with Docker
I tried to verify yum-cron
I tried Jets (ruby serverless)
I tried to get started with Swagger using Spring Boot
I tried metaprogramming in Java
I tried Spring Boot introductory guide [Building a RESTful Web Service]
I tried printing a form with Spring MVC and JasperReports 1/3 (JasperReports settings)
I tried connecting to MySQL using JDBC Template with Spring MVC
I tried to create a Spring MVC development environment on Mac
Spring Boot Introductory Guide I tried [Consuming a RESTful Web Service]