15. Using Scopes

Support for scopes in a state machine is very limited but it is possible to enable use of session scope using a normal spring @Scope annotation. Firstly if state machine is build manually via a builder and returned into context as @Bean, and secondly via a configuration adapter. Both of these simply needs an a @Scope to be present where scopeName is set to session and proxyMode to ScopedProxyMode.TARGET_CLASS. Examples for both use cases are shown below.

[Tip]Tip

See sample Chapter 45, Scope how to use session scoping.

@Configuration
public class Config3 {

    @Bean
    @Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
    StateMachine<String, String> stateMachine() throws Exception {
        Builder<String, String> builder = StateMachineBuilder.builder();
        builder.configureConfiguration()
            .withConfiguration()
                .autoStartup(true)
                .taskExecutor(new SyncTaskExecutor());
        builder.configureStates()
            .withStates()
                .initial("S1")
                .state("S2");
        builder.configureTransitions()
            .withExternal()
                .source("S1")
                .target("S2")
                .event("E1");
        StateMachine<String, String> stateMachine = builder.build();
        return stateMachine;
    }

}
@Configuration
@EnableStateMachine
@Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public static class Config4 extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config
            .withConfiguration()
                .autoStartup(true);
    }

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states
            .withStates()
                .initial("S1")
                .state("S2");
    }

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

}

Once you have scoped state machine into session, autowiring it into a @Controller will give new state machine instance per session. State machine is then destroyed when HttpSession is invalidated.

@Controller
public class StateMachineController {

    @Autowired
    StateMachine<String, String> stateMachine;

    @RequestMapping(path="/state", method=RequestMethod.POST)
    public HttpEntity<Void> setState(@RequestParam("event") String event) {
        stateMachine.sendEvent(event);
        return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
    }

    @RequestMapping(path="/state", method=RequestMethod.GET)
    @ResponseBody
    public String getState() {
        return stateMachine.getState().getId();
    }
}
[Note]Note

Using state machines in a session scopes needs a careful planning mostly because it is a relatively heavy component.

[Note]Note

Spring Statemachine poms don’t have any dependencies to Spring MVC classes which you will need to work with session scope. But if you’re working with a web application, you’ve already pulled those deps directly from Spring MVC or Spring Boot.