There are use cases when state machine needs to be created dynamically instead of defining static configuration at compile time. For example if there are custom components which are using its own state machines and these components are created dynamically it is impossible to have a static state machined build during the application start. Internally state machines are always build via a factory interfaces and this then gives user an option to use this feature programmatically. Configuration for state machine factory is exactly same as you’ve seen in various examples in this document where state machine configuration is hard coded.
Actually creating a state machine using @EnableStateMachine will work via factory so @EnableStateMachineFactory is merely exposing that factory via its interface.
@Configuration @EnableStateMachineFactory public class Config6 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S1) .end(States.SF) .states(EnumSet.allOf(States.class)); } }
Now that you’ve used @EnableStateMachineFactory to create a factory instead of a state machine bean, it can be injected and used as is to request new state machines.
public class Bean3 { @Autowired StateMachineFactory<States, Events> factory; void method() { StateMachine<States,Events> stateMachine = factory.getStateMachine(); stateMachine.start(); } }
Current limitation of factory is that all actions and guard it is associating with created state machine will share a same instances. This means that from your actions and guard you will need to specifically handle a case that same bean will be called by a different state machines. This limitation is something which will be resolved in future releases.
Using adapters shown above has a limitation imposed by its
requirement to work via Spring @Configuration
classes and
application context. While this is a very clear model to configure a
state machine instances it will limit configuration at a compile time
which is not always what a user wants to do. If there is a requirement
to build more dynamic state machines, a simple builder pattern can be
used to construct similar instances. Using strings as states and
events this builder pattern can be used to build fully dynamic state
machines outside of a Spring application context as shown above.
StateMachine<String, String> buildMachine1() throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureStates() .withStates() .initial("S1") .end("SF") .states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4"))); return builder.build(); }
Builder is using same configuration interfaces behind the scenes that
the @Configuration
model using adapter classes. Same model goes to
configuring transitions, states and common configuration via builder’s
methods. This simply means that whatever you can use with a normal
EnumStateMachineConfigurerAdapter
or StateMachineConfigurerAdapter
can be used dynamically via a builder.
Note | |
---|---|
Currently |
StateMachine<String, String> buildMachine2() throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureConfiguration() .withConfiguration() .autoStartup(false) .beanFactory(null) .taskExecutor(null) .taskScheduler(null) .listener(null); return builder.build(); }
It is important to understand on what cases common configuration needs
to be used with a machines instantiated from a builder. Configurer
returned from a withConfiguration()
can be used to setup autoStart,
TaskScheduler, TaskExecutor, BeanFactory and additionally register
a StateMachineListener. If StateMachine instance returned from
a builder is registered as a bean via @Bean
, i.e. BeanFactory
is attached automatically and then a default TaskExecutor can be found
from there. If instances are used outside of a spring application context
these methods must be used to setup needed facilities.