{"id":7019,"date":"2021-03-23T04:00:28","date_gmt":"2021-03-23T08:00:28","guid":{"rendered":"https:\/\/springframework.guru\/?p=7019"},"modified":"2021-09-10T08:52:13","modified_gmt":"2021-09-10T12:52:13","slug":"spring-state-machine","status":"publish","type":"post","link":"https:\/\/springframework.guru\/spring-state-machine\/","title":{"rendered":"Spring State Machine"},"content":{"rendered":"<p>The Spring framework has a library called Spring State Machine which is a model of computation that depends on the finite states.<\/p>\n<p>It can only be in one state at a given time, hence it is also called a finite state machine.<\/p>\n<p>The transitions of this State Machine are the changes in the status of the state.<\/p>\n<p>In the implementation of any software project, you will always require a well-formed application architecture. Besides this, the core functionalities along with the application states need to be managed well. To manage these states, Spring State Machine is always a good choice. This interaction of the states with the Spring State Machine occurs by sending an event, listening for changes, or simply requesting a current state.<\/p>\n<p>In this post, I will introduce you to the Spring State Machine.<\/p>\n<h2>Maven Dependency<\/h2>\n<p>Spring State Machine requires a dependency on the core module <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">spring-statemachine-core<\/code> to be added in your <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">pom.xml<\/code> file.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\">&lt;dependency&gt;\r\n   &lt;groupId&gt;org.springframework.statemachine&lt;\/groupId&gt;\r\n   &lt;artifactId&gt;spring-statemachine-core&lt;\/artifactId&gt;\r\n   &lt;version&gt;2.1.3.RELEASE&lt;\/version&gt;\r\n&lt;\/dependency&gt;\r\n<\/pre>\n<h2>Spring State Machine Example<\/h2>\n<p>SSM or Spring State Machine has 2 ways of implementing states and events.<\/p>\n<ul>\n<li><strong>String<\/strong><\/li>\n<li><strong>Enumeration<\/strong><\/li>\n<\/ul>\n<h2>Defining the Events and States<\/h2>\n<p>In <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">Enumeration<\/code>, states and events are hardcoded to for type safety.<\/p>\n<p>This is the code for events of the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">BlogEvents<\/code> Enumeration.<\/p>\n<p><strong>BlogEvents<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\">package guru.springframework.domain;\r\n\r\npublic enum BlogEvents {\r\n   EDIT_BLOG,\r\n   PUBLISH_BLOG\r\n<\/pre>\n<p>This is the code for states of the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">BlogStates<\/code> Enumeration.<\/p>\n<p><strong>BlogStates<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\">package guru.springframework.domain;\r\n\r\npublic enum BlogStates {\r\n   UNEDITED_BLOG,\r\n   EDITING_IN_PROGRESS,\r\n   BLOG_EDITED,\r\n   BLOG_PUBLISHED\r\n}\r\n<\/pre>\n<h2>Configuring the State Machine<\/h2>\n<p>The next step is to configure the Spring State Machine.<\/p>\n<p>The code for the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">StateMachineConfig<\/code> class is this.<\/p>\n<p><strong>StateMachineConfig.java<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\">@Configuration\r\n@EnableStateMachine\r\npublic class StateMachineConfig extends EnumStateMachineConfigurerAdapter&lt;BlogStates, BlogEvents&gt; {\r\n\r\n   @Override\r\n   public void configure(StateMachineStateConfigurer&lt;BlogStates, BlogEvents&gt; states) throws Exception {\r\n       states.withStates()\r\n             .initial(BlogStates.UNEDITED_BLOG)\r\n             .states(EnumSet.allOf(BlogStates.class));\r\n   }\r\n\r\n @Override\r\n   public void configure(StateMachineTransitionConfigurer&lt;BlogStates, BlogEvents&gt; transitions) throws Exception {\r\n       transitions\r\n              .withExternal()\r\n              .source(BlogStates.UNEDITED_BLOG)\r\n              .target(BlogStates.EDITING_IN_PROGRESS)\r\n              .event(BlogEvents.EDIT_BLOG)\r\n              .and()\r\n              .withExternal()\r\n              .source(BlogStates.EDITING_IN_PROGRESS)\r\n              .target(BlogStates.BLOG_EDITED)\r\n              .event(BlogEvents.EDIT_BLOG)\r\n              .and()\r\n              .withExternal()\r\n              .source(BlogStates.BLOG_EDITED)\r\n              .target(BlogStates.BLOG_PUBLISHED)\r\n              .event(BlogEvents.PUBLISH_BLOG)\r\n              .and()\r\n              .withExternal()\r\n              .source(BlogStates.BLOG_PUBLISHED)\r\n              .target(BlogStates.BLOG_EDITED)\r\n              .event(BlogEvents.EDIT_BLOG) ;\r\n   }\r\n\r\n@Override\r\npublic void configure(StateMachineConfigurationConfigurer&lt;BlogStates, BlogEvents&gt; config) throws Exception {\r\n   config.withConfiguration()\r\n         .autoStartup(true)\r\n         .listener(new Listener());\r\n}\r\n<\/pre>\n<p>Line 1 uses <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">@Configuration<\/code> annotation to specify that it is a Configuration class.<\/p>\n<p>Line 2 has <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">@EnableStateMachine<\/code> annotation which will automatically create a default state machine when the application starts.<\/p>\n<p>In some scenarios it is useful to start an instance of State Machine depending on a business logic. In that case the configuration must be annotated as a factory with <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">@EnableStateMachineFactory<\/code> annotation.<\/p>\n<p>The <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">@Configuration<\/code>class extends adapters <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">(EnumStateMachineConfigurerAdapter or StateMachineConfigurerAdapter)<\/code>, which lets you override configuration callback methods.<\/p>\n<p>Here, in Line 6 &#8211; I have extended <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">EnumStateMachineConfigurerAdapter<\/code> and overridden 3 methods.<\/p>\n<p>In Line 8 &#8211; 9 the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">State-configurer method()<\/code> adds the states using a configuration method while initial- and end- states are optional and can be omitted. The initial state is assigned immediately after the State Machine is created.<\/p>\n<p>Line 13 &#8211; 33 Using <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">StateMachineTransitionConfigurer<\/code> interface you mention transitions from one state(source) to another state(target) based on events triggered (event) . <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">withExternal()<\/code> is transition from one state to another state where as <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">withInternal()<\/code> is transition from state to same state.<\/p>\n<p>In Line 38-39 I specified properties for autostartup and added a listener for listening events using <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">StateMachineConfigurationConfigurer<\/code> interface as argument. It is used to track state changes and here it is only used to print out the current state.<\/p>\n<h2>Setting up the Listener class<\/h2>\n<p>The code for the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">Listener Class<\/code> is this.<\/p>\n<p><strong>Listener.java<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\">public class Listener extends StateMachineListenerAdapter&lt;BlogStates,BlogEvents&gt; {\r\n   @Override\r\n   public void stateChanged(State&lt;BlogStates, BlogEvents&gt; from,     State&lt;BlogStates, BlogEvents&gt; to) {\r\n       System.out.println(\"state changed from \" + to.getId());\r\n   }\r\n}<\/pre>\n<p>Here, the listener is built on a separate adapter <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">StateMachineListenerAdapter<\/code>.<\/p>\n<h2>Setup and Run the Application<\/h2>\n<p>After the configuration is done, a typed instance of <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">StateMachine&lt;States, Event&gt;<\/code> can immediately be accessed and autowired to the business logic. An initial state <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">States.UNEDITED<\/code> is already triggered.<\/p>\n<p>Driving a State Machine is realized via transitions that are triggered by the appropriate events.<\/p>\n<p>The code for the <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">SpringStateApplication<\/code> class is this.<\/p>\n<p><strong>SpringStateApplication.java<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\"> @SpringBootApplication\r\n@EnableStateMachine\r\npublic class SpringStateMachineApplication implements CommandLineRunner {\r\n \r\n   private final StateMachine&lt;BlogStates, BlogEvents&gt; stateMachine;\r\n \r\n   @Autowired\r\n   public SpringStateMachineApplication(StateMachine&lt;BlogStates, BlogEvents&gt; stateMachine) {\r\n       this.stateMachine = stateMachine;\r\n   }\r\n \r\n   public static void main(String[] args) {\r\n       SpringApplication.run(SpringStateMachineApplication.class, args);\r\n   }\r\n \r\n   @Override\r\n   public void run(String... args) {\r\n       stateMachine.start();\r\n       stateMachine.sendEvent(BlogEvents.EDIT_BLOG);\r\n       stateMachine.sendEvent(BlogEvents.PUBLISH_BLOG);\r\n       stateMachine.stop();\r\n   } \r\n}<\/pre>\n<p>In the run method, you can send events using State Machine in 2 ways.<\/p>\n<p>Here, in Line 19-20 I have used the first method of sending a type safe event using the state machine api method.<\/p>\n<p>The other way is to send event wrapped in a Message using api method with custom event headers.<\/p>\n<p><a href=\"http:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-7030\" src=\"http:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-1024x348.png\" alt=\"Output Image\" width=\"1024\" height=\"348\" srcset=\"https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-1024x348.png 1024w, https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-300x102.png 300w, https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-768x261.png 768w, https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-848x288.png 848w, https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm-410x139.png 410w, https:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/Output1_sm.png 1323w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p>The state gets changed on running the application successfully.\u00a0 The target <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">EDITING_IN_PROGRESS<\/code>is achieved here.<\/p>\n<h2>Summary<\/h2>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">States<\/code> and <code class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"git\" data-enlighter-linenumbers=\"false\">events<\/code> within a single Spring State Machine are really simple to understand. Spring State Machine finds its uses in IOT, Bots and many more areas of application. To check if the transition is allowed or not, you can nest the states and configure the guards and the events can be triggered by the actions, or on a timer.<\/p>\n<p>The topic is just scratched here, there are ample amount of things to explore further.<\/p>\n<p>You can find the source code of this post on <a href=\"https:\/\/github.com\/spring-framework-guru\/sfg-blog-posts\/tree\/master\/spring-state-machine\" target=\"_blank\" rel=\"noopener\">Github<\/a><\/p>\n<p>To have a deeper insight on the Spring State Machine, you can check my Udemy Best Seller Course on <a href=\"https:\/\/www.udemy.com\/course\/spring-boot-microservices-with-spring-cloud-beginner-to-guru\/?referralCode=6142D427AE53031FEF38\" target=\"_blank\" rel=\"noopener\">Spring Boot Microservices with Spring Cloud Beginner to Guru<\/a><\/p>\n<p><a href=\"https:\/\/www.udemy.com\/course\/spring-boot-microservices-with-spring-cloud-beginner-to-guru\/?referralCode=6142D427AE53031FEF38\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-7006 size-full\" src=\"http:\/\/springframework.guru\/wp-content\/uploads\/2021\/03\/microservices.jpg\" alt=\"\" width=\"240\" height=\"135\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Spring framework has a library called Spring State Machine which is a model of computation that depends on the finite states. It can only be in one state at a given time, hence it is also called a finite state machine. The transitions of this State Machine are the changes in the status of [&hellip;]<a href=\"https:\/\/springframework.guru\/spring-state-machine\/\" class=\"df-link-excerpt\">Continue reading<\/a><\/p>\n","protected":false},"author":111,"featured_media":4653,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_lmt_disableupdate":"","_lmt_disable":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[21],"tags":[377,375,376],"class_list":["post-7019","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-spring","tag-events","tag-spring-state-machine","tag-states"],"jetpack_publicize_connections":[],"aioseo_notices":[],"modified_by":"jt","jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/springframework.guru\/wp-content\/uploads\/2017\/07\/Banner560x292_08Web.jpg","jetpack_shortlink":"https:\/\/wp.me\/p5BZrZ-1Pd","_links":{"self":[{"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/posts\/7019"}],"collection":[{"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/users\/111"}],"replies":[{"embeddable":true,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/comments?post=7019"}],"version-history":[{"count":17,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/posts\/7019\/revisions"}],"predecessor-version":[{"id":7603,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/posts\/7019\/revisions\/7603"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/media\/4653"}],"wp:attachment":[{"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/media?parent=7019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/categories?post=7019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/springframework.guru\/wp-json\/wp\/v2\/tags?post=7019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}