Spring Statemachine

它的主要功能是帮助开发者简化状态机的开发过程,让状态机结构更加层次化。

特性

Spring Statemachine旨在提供以下功能:

  • 对于简单的用例,易于使用平坦的一级状态机。

  • 分层状态机结构,简化复杂的状态配置。

  • 状态机区域提供更复杂的状态配置。

  • 触发器、转换、保护和操作的使用。

  • 类型安全配置适配器。

  • 构建器模式,用于在Spring应用程序上下文之外方便地实例化

  • 常用用例的配方

  • 分布式状态机基于一个动物园管理员

  • 状态机事件监听器。

  • UML Eclipse Papyrus建模。

  • 将机器配置存储在持久存储中。

  • Spring IOC集成以将bean与状态机关联。

状态机很强大,因为行为总是被保证是一致的,这使得调试相对容易。

这是因为操作规则是在机器启动时写死的。

其思想是,应用程序可能以有限的状态存在,某些预定义的触发器可以将应用程序从一种状态带到另一种状态。这样的触发器可以基于事件或计时器。

在应用程序之外定义高级逻辑,然后依赖状态机来管理状态,这要容易得多。

您可以通过发送事件、侦听更改或简单地请求当前状态与状态机交互。

快速开始

项目结构

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
. └── com └── github └── houbb └── spring └── boot └── statemachine ├── Application.java ├── config │   └── StateMachineConfig.java └── constant ├── Events.java └── States.java

文件配置

  • pom.xml
  [xml]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>2.0.0.RELEASE</version> </dependency> </dependencies>

代码编写

  • 状态定义
  [java]
1
2
3
4
5
6
7
8
9
10
11
public enum States { // 待支付 UNPAID, // 待收货 WAITING_FOR_RECEIVE, // 结束 DONE }
  • 事件定义
  [java]
1
2
3
4
5
6
7
8
9
10
public enum Events { /** * 支付 会触发状态从待支付 UNPAID 状态到待收货 WAITING_FOR_RECEIVE 状态的迁移, */ PAY, /** * 收货 会触发状态从待收货 WAITING_FOR_RECEIVE 状态到结束 DONE 状态的迁移。 */ RECEIVE }
  • StateMachineConfig.java
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import com.github.houbb.spring.boot.statemachine.constant.Events; import com.github.houbb.spring.boot.statemachine.constant.States; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.listener.StateMachineListener; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.transition.Transition; import java.util.EnumSet; @Configuration @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 初始化当前状态机拥有哪些状态 * @param states * @throws Exception */ @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states.withStates() .initial(States.UNPAID) .states(EnumSet.allOf(States.class)); } /** * 方法用来初始化当前状态机有哪些状态迁移动作,其中命名中我们很容易理解每一个迁移动作,都有来源状态source, * 目标状态target以及触发事件event。 * @param transitions * @throws Exception */ @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions.withExternal() .source(States.UNPAID).target(States.WAITING_FOR_RECEIVE) .event(Events.PAY) .and() .withExternal() .source(States.WAITING_FOR_RECEIVE).target(States.DONE) .event(Events.RECEIVE); } /** * 方法为当前的状态机指定了状态监听器 * @param config * @throws Exception */ @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config.withConfiguration() .listener(listener()); } @Bean public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { @Override public void transition(Transition<States, Events> transition) { if (transition.getTarget().getId() == States.UNPAID) { logger.info("订单创建,待支付"); return; } if (transition.getSource().getId() == States.UNPAID && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) { logger.info("用户完成支付,待收货"); return; } if (transition.getSource().getId() == States.WAITING_FOR_RECEIVE && transition.getTarget().getId() == States.DONE) { logger.info("用户已收货,订单完成"); return; } } }; } }
  • Application.java
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.github.houbb.spring.boot.statemachine.constant.Events; import com.github.houbb.spring.boot.statemachine.constant.States; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.statemachine.StateMachine; @SpringBootApplication public class Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Autowired private StateMachine<States, Events> stateMachine; @Override public void run(String... args) throws Exception { stateMachine.start(); stateMachine.sendEvent(Events.PAY); stateMachine.sendEvent(Events.RECEIVE); } }

测试日志

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
... 2018-09-26 16:47:52.273 INFO 29446 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 2018-09-26 16:47:52.284 INFO 29446 --- [ main] eConfig$$EnhancerBySpringCGLIB$$1dfef0ee : 订单创建,待支付 2018-09-26 16:47:52.287 INFO 29446 --- [ main] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@528c868 2018-09-26 16:47:52.288 INFO 29446 --- [ main] o.s.s.support.LifecycleObjectSupport : started UNPAID WAITING_FOR_RECEIVE DONE / UNPAID / uuid=064443bf-d8e4-44b0-9ae9-b06c57cc5620 / id=null 2018-09-26 16:47:52.293 INFO 29446 --- [ main] eConfig$$EnhancerBySpringCGLIB$$1dfef0ee : 用户完成支付,待收货 2018-09-26 16:47:52.294 INFO 29446 --- [ main] eConfig$$EnhancerBySpringCGLIB$$1dfef0ee : 用户已收货,订单完成 2018-09-26 16:47:52.296 INFO 29446 --- [ main] c.g.h.s.boot.statemachine.Application : Started Application in 11.139 seconds (JVM running for 11.67) 2018-09-26 16:47:52.297 INFO 29446 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4516af24: startup date [Wed Sep 26 16:47:51 CST 2018]; root of context hierarchy 2018-09-26 16:47:52.298 INFO 29446 --- [ Thread-2] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 0 2018-09-26 16:47:52.299 INFO 29446 --- [ Thread-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@528c868 2018-09-26 16:47:52.299 INFO 29446 --- [ Thread-2] o.s.s.support.LifecycleObjectSupport : stopped UNPAID WAITING_FOR_RECEIVE DONE / / uuid=064443bf-d8e4-44b0-9ae9-b06c57cc5620 / id=null ...

个人感受

这个在状态流转中比较适合使用。

可以使得代码逻辑变得清晰优雅。

参考资料

https://projects.spring.io/spring-statemachine/

http://blog.didispace.com/spring-statemachine/