javaspringspring-statemachine

StateMachine - actions on states change


I wrote the implementation of the state machine, using the Spring guide.

But I can’t achieve any reaction for changing the state, although the states themselves change successfully. Perhaps I misunderstood the goal of the Beans class? I need to achieve automatic execution of the closeDoor() and startMoving() methods when the state changes.

These messages in the methods on the console are not displayed:

import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;

@WithStateMachine
public class Beans {

  @OnTransition(target = "CLOSED_DOOR")
  void closeDoor() {
      System.out.println("closeDoor method");
  }

  @OnTransition(target = "GOING")
  void startMoving() {
      System.out.println("startMoving method");
  }
}

Configuration:

import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;;

import java.util.EnumSet;


@org.springframework.context.annotation.Configuration
@EnableStateMachine
public class Configuration extends EnumStateMachineConfigurerAdapter<States, Events> {
    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.STAY)
                .states(EnumSet.allOf(States.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.STAY).target(States.CLOSED_DOOR)
                .event(Events.CLOSE_DOOR)
                .and()
                .withExternal()
                .source(States.CLOSED_DOOR).target(States.GOING)
                .event(Events.MOVE);
    }
}

And the launch (the states are displayed correct in the console, according to the events):

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;

public class App {
    @Autowired
    static StateMachine<States, Events> stateMachine;

    public static void main(String[] args) {

        stateMachine = Builder.getMachine();
        stateMachine.start();

        stateMachine.sendEvent(Events.CLOSE_DOOR);
        System.out.println(stateMachine.getState()); // ObjectState [getIds()=[CLOSED_DOOR]

        stateMachine.sendEvent(Events.MOVE);
        System.out.println(stateMachine.getState()); // ObjectState [getIds()=[GOING]
    }

Dependency is only one.

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

What i'm doing wrong?


Solution

  • Finally, I've managed to make it work. Your problem was Spring DI mechanism. You were trying to use @WithStateMachine to enable transition listeners but then you were using .getMachine() to create the machine object. It will not work like that, you need to decide whether you want to use Spring Context or not. I've created a solution using context, but as well you can leave it and use just manual builder, but then you will need to change your listeners to use a manual method instead of Spring Context annotations.

    Change your main class to:

    public class App {
    
        public static void main(String[] args) {
    
            ConfigurableApplicationContext context = new AnnotationConfigApplicationContext("machine");
            final StateMachine<States, Events> stateMachine = context.getBean(StateMachine.class);
    
            stateMachine.start();
            System.out.println(stateMachine.getState()); // ObjectState [getIds()=[STAY]
    
            stateMachine.sendEvent(Events.CLOSE_DOOR);
            System.out.println(stateMachine.getState()); // ObjectState [getIds()=[CLOSED_DOOR]
    
            stateMachine.sendEvent(Events.MOVE);
            System.out.println(stateMachine.getState()); // ObjectState [getIds()=[GOING]
        }
    }
    

    Let me know if it works for you and if you understand it.