Spring Statemachine

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

特性

Spring Statemachine旨在提供以下功能:

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

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

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

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

  • 类型安全配置适配器。

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

  • 常用用例的配方

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

  • 状态机事件监听器。

  • UML Eclipse Papyrus建模。

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

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

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

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

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

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

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

快速开始

项目结构

.
└── com
    └── github
        └── houbb
            └── spring
                └── boot
                    └── statemachine
                        ├── Application.java
                        ├── config
                        │   └── StateMachineConfig.java
                        └── constant
                            ├── Events.java
                            └── States.java

文件配置

  • pom.xml
<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>

代码编写

  • 状态定义
public enum  States {

    // 待支付
    UNPAID,

    // 待收货
    WAITING_FOR_RECEIVE,

    // 结束
    DONE
}
  • 事件定义
public enum Events {
    /**
     * 支付   会触发状态从待支付 UNPAID 状态到待收货 WAITING_FOR_RECEIVE 状态的迁移,
     */
    PAY,
    /**
     * 收货   会触发状态从待收货 WAITING_FOR_RECEIVE 状态到结束 DONE 状态的迁移。
     */
    RECEIVE
}
  • StateMachineConfig.java
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
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);
    }
}

测试日志

...
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/