5

I'm currently building up a little sample project with Spring state machine.

My configuration:

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

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.LOCKED)
                .states(EnumSet.allOf(States.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> 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);
    }

    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {
            @Override
            public void stateChanged(State<States, Events> from, State<States, Events> to) {
                System.out.println("State change to " + to.getId());
            }
        };
    }
}

When I now try to inject the state machine with

@Autowired
StateMachine<States, Events> stateMachine;

IntelliJ gives me the hint, that this could not be autowired because no bean is there. I also get an exception if I run the Application.

The gradle dependency:

compile 'org.springframework.statemachine:spring-statemachine-core:2.0.1.
compile group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.0.1.RELEASE'
compile group: 'org.springframework.shell', name: 'spring-shell-starter', version: '2.0.1.RELEASE'
compile group: 'org.springframework.statemachine', name: 'spring-statemachine-boot', version: '1.2.11.RELEASE'
Daniel Eisenreich
  • 1,353
  • 3
  • 16
  • 32

5 Answers5

1

I don't know what the problem was.. But the following snippet works for me.

@Configuration
@EnableStateMachine
public class StateMachineConfiguration {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean
    public StateMachine<States, Events> stateMachine(StateMachineListener<States, Events> listener) throws Exception {
        StateMachineBuilder.Builder<States, Events> builder = StateMachineBuilder.builder();

        builder.configureStates()
            .withStates()
            .initial(States.LOCKED)
            .states(EnumSet.allOf(States.class));

        builder.configureTransitions()
            .withExternal()
            .source(States.LOCKED)
            .target(States.UNLOCKED)
            .event(Events.COIN)
            .and()
            .withExternal()
            .source(States.UNLOCKED)
            .target(States.LOCKED)
            .event(Events.PUSH);

        StateMachine<States, Events> stateMachine = builder.build();
        stateMachine.addStateListener(listener);
        return stateMachine;
    }

    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {
            @Override
            public void stateChanged(State<States, Events> from, State<States, Events> to) {
                logger.info("State change to " + to.getId());
            }
        };
    }
}
Daniel Eisenreich
  • 1,353
  • 3
  • 16
  • 32
  • The snippet works because you are creating StateMachine `@Bean` yourself. Intellij can locate it. That said, if StateMachine is `@Autowired`, Spring will automatically instantiate a default StateMachine bean if one was not defined as a `@Bean`. Credit goes to @guidadic, but he can't comment on posts of other users', yet. – Andrei Konstantinov Jun 01 '19 at 22:54
  • As an addition, you could have this error: ```The bean 'stateMachine' could not be registered. A bean with that name has already been defined in class path resource [com/tests/springboottests/stateMachine/StateMachineConfig.class] and overriding is disabled.``` The solution for this: ```spring.main.allow-bean-definition-overriding=true``` in ```application.properties``` – Evghenii Orenciuc Mar 15 '20 at 11:01
1

For me the issue was caused by not matching the generic type parameters. So instead of StateMachineStateConfigurer<States, Events> I had StateMachineStateConfigurer<String, String> so that the <States, Events> version of the bean was not defined.

In your case check the import of "States" and the import of "Events" to make sure they are the same classes in the bean definition and where you use it with @Autowired.

tangens
  • 39,095
  • 19
  • 120
  • 139
Nick
  • 328
  • 2
  • 10
0

You can try using

@EnableStateMachine(name = "myStateMachine")

and

@Autowired
StateMachine<States, Events> myStateMachine;
razi
  • 41
  • 4
0

I have the same problem.

I done that.

It's working

@SpringBootApplication
@EnableStateMachine
public class StateMachineSpringBootApplication {

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

}

config

@Configuration
public class MachineConfigurationCustom extends EnumStateMachineConfigurerAdapter<BookStates, BookEvents> {

    @Bean
    public StateMachine<BookStates, BookEvents> stateMachine(StateMachineListener<BookStates, BookEvents> listener) throws Exception {

        StateMachineBuilder.Builder<BookStates, BookEvents>  builder =
                StateMachineBuilder.builder();

        builder.configureStates()
                .withStates()
                .initial(BookStates.AVAILABLE)
                .states(EnumSet.allOf(BookStates.class));

        builder.configureTransitions()
                .withExternal()
                .source(BookStates.AVAILABLE)
                .target(BookStates.BORROWED)
                .event(BookEvents.BORROW)
                .and()
                .withExternal()
                .source(BookStates.BORROWED)
                .target(BookStates.AVAILABLE)
                .event(BookEvents.RETURN)
                .and()
                .withExternal()
                .source(BookStates.AVAILABLE)
                .target(BookStates.IN_REPAIR)
                .event(BookEvents.START_REPAIR)
                .and()
                .withExternal()
                .source(BookStates.IN_REPAIR)
                .target(BookStates.AVAILABLE)
                .event(BookEvents.END_REPAIR);

        StateMachine<BookStates, BookEvents> stateMachine = builder.build();
        stateMachine.addStateListener(listener);
        return stateMachine;
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<BookStates, BookEvents> config) throws Exception {
        config.withConfiguration()
                .autoStartup(true)
                .listener(new LoggingMachineListener());
    }


}

listener

@Component
public class LoggingMachineListener implements StateMachineListener<BookStates, BookEvents> {
    private static final Logger LOGGER = LoggingUtils.LOGGER;

    @Override
    public void stateChanged(State<BookStates, BookEvents> from, State<BookStates, BookEvents> to) {
        LOGGER.info("State changed from {} to {}", getStateInfo(from), getStateInfo(to));
    }

    @Override
    public void stateEntered(State<BookStates, BookEvents> state) {
        LOGGER.info("Entered state {}", getStateInfo(state));
    }

    @Override
    public void stateExited(State<BookStates, BookEvents> state) {
        LOGGER.info("Exited state {}", getStateInfo(state));
    }

    @Override
    public void stateMachineStarted(StateMachine<BookStates, BookEvents> stateMachine) {
        LOGGER.info("Machine started: {}", stateMachine);
    }

    @Override
    public void stateMachineStopped(StateMachine<BookStates, BookEvents> stateMachine) {
        LOGGER.info("Machine stopped: {}", stateMachine);
    }

    @Override
    public void stateMachineError(StateMachine<BookStates, BookEvents> stateMachine, Exception exception) {
        LOGGER.error("Machine error: {}", stateMachine);
    }

    @Override
    public void extendedStateChanged(Object key, Object value) {
        LOGGER.info("Extended state changed: [{}: {}]", key, value);
    }

......
}

runner

@Component
public class RunnerStateMachineWithCommandLine implements CommandLineRunner {

    private final StateMachine<BookStates, BookEvents> stateMachine;

    @Autowired
    public RunnerStateMachineWithCommandLine(StateMachine<BookStates, BookEvents> stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public void run(String... args){
        stateMachine.start();
        stateMachine.sendEvent(BookEvents.RETURN);
        stateMachine.sendEvent(BookEvents.BORROW);
        stateMachine.stop();
    }
}
skyho
  • 1,438
  • 2
  • 20
  • 47
  • I offered the solution. It's working. But why do I need to explicitly define @Bean public State Machine ? May be do anybody know Why does it is happened ? – skyho Nov 30 '20 at 14:55
  • There are many examples where this approach is not used, but rather the annotation is sufficient @EnableStateMachine . – skyho Nov 30 '20 at 15:01
0

In the configuration class, you can add the annotation @EnableStateMachineFactory and it will work,

@EnableStateMachineFactory
@Configuration
@Log
public class EmployeeStateMachineConfig extends StateMachineConfigurerAdapter<EmployeeStates, EmployeeEvents> {


}
Arefe
  • 11,321
  • 18
  • 114
  • 168