状态机中,每个状态有着相应的行为,随着行为的触发来切换状态。其中一种做法是使用二维数组实现状态机机制,其中横坐标表示行为,纵坐标表示状态,具体的数值则表示当前的状态。

我们以登录场景设计一个状态机。

状态机设计_成长专栏

这时,我们设计一张状态机表。

状态机设计_成长专栏_02

那么,此时它的二维数组,如下所示。

状态机设计_成长专栏_03

此外,我们也可以通过状态模式实现一个状态机。状态模式将每一个状态封装成独立的类,具体行为会随着内部状态而改变。状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态,避免了冗长的条件分支语句,让系统具有更好的灵活性和可扩展性。

现在,我们定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 4 种状态。

 
  1. public enum StateEnum {

  2.    // 未连接

  3.    UNCONNECT(1, "UNCONNECT"),

  4.    // 已连接

  5.    CONNECT(2, "CONNECT"),

  6.    // 注册中

  7.    REGISTING(3, "REGISTING"),

  8.    // 已注册

  9.    REGISTED(4, "REGISTED");

  10.  

  11.    private int key;

  12.    private String value;

  13.  

  14.    StateEnum(int key, String value) {

  15.        this.key = key;

  16.        this.value = value;

  17.    }

  18.    public int getKey() {return key;}

  19.    public String getValue() {return value;}

  20. }

定义一个环境类,它是实际上是真正拥有状态的对象。

 
  1. public class Context {

  2.    private State state;

  3.    public void connect(){

  4.        state.connect(this);

  5.        System.out.println("STATE : " + state.getCurState());

  6.    }

  7.    public void register(){

  8.        state.register(this);

  9.        System.out.println("STATE : " + state.getCurState());

  10.    }  

  11.    public void registerSuccess(){

  12.        state.registerSuccess(this);

  13.        System.out.println("STATE : " + state.getCurState());

  14.    }

  15.    public void registerFailed(){

  16.        state.registerFailed(this);

  17.        System.out.println("STATE : " + state.getCurState());

  18.    }

  19.    public void unRegister(){

  20.        state.unRegister(this);

  21.        System.out.println("STATE : " + state.getCurState());

  22.    }

  23.    public State getState() {

  24.        return state;

  25.    }

  26.    public void setState(State state) {

  27.        this.state = state;

  28.    }

  29. }

状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态。现在,我们定义几个状态类。

 
  1. public interface State {

  2.    void connect(Context c);

  3.    void register(Context c);

  4.    void registerSuccess(Context c);

  5.    void registerFailed(Context c);

  6.    void unRegister(Context c);

  7.    String getCurState();

  8. }

  9.  

  10. public class UnconnectState implements State {

  11.    @Override

  12.    public void connect(Context c) {

  13.        c.setState(new ConnectState());

  14.    }

  15.    @Override

  16.    public void register(Context c) {

  17.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  18.    }

  19.    @Override

  20.    public void registerSuccess(Context c) {

  21.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  22.    }

  23.    @Override

  24.    public void registerFailed(Context c) {

  25.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  26.    }

  27.    @Override

  28.    public void unRegister(Context c) {

  29.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  30.    }

  31.    @Override

  32.    public String getCurState() {

  33.        return StateEnum.UNCONNECT.toString();

  34.    }

  35. }

  36.  

  37. public class ConnectState implements State {

  38.    @Override

  39.    public void connect(Context c) {

  40.        c.setState(new ConnectState());

  41.    }

  42.    @Override

  43.    public void register(Context c) {

  44.        c.setState(new RegistingState());

  45.    }

  46.    @Override

  47.    public void registerSuccess(Context c) {

  48.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  49.    }

  50.    @Override

  51.    public void registerFailed(Context c) {

  52.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  53.    }

  54.    @Override

  55.    public void unRegister(Context c) {

  56.        c.setState(new UnconnectState());

  57.    }

  58.    @Override

  59.    public String getCurState() {

  60.        return StateEnum.CONNECT.toString();

  61.    }

  62. }

  63.  

  64. public class RegistingState implements State {

  65.    @Override

  66.    public void connect(Context c) {

  67.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  68.    }

  69.    @Override

  70.    public void register(Context c) {

  71.        c.setState(new RegistingState());

  72.    }

  73.    @Override

  74.    public void registerSuccess(Context c) {

  75.        c.setState(new RegistedState());

  76.    }

  77.    @Override

  78.    public void registerFailed(Context c) {

  79.        c.setState(new UnconnectState());

  80.    }

  81.    @Override

  82.    public void unRegister(Context c) {

  83.        c.setState(new UnconnectState());

  84.    }

  85.    @Override

  86.    public String getCurState() {

  87.        return StateEnum.REGISTING.toString();

  88.    }

  89. }

  90.  

  91. public class RegistedState implements State {

  92.    @Override

  93.    public void connect(Context c) {

  94.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  95.    }

  96.    @Override

  97.    public void register(Context c) {

  98.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  99.    }

  100.    @Override

  101.    public void registerSuccess(Context c) {

  102.        c.setState(new RegistedState());

  103.    }

  104.    @Override

  105.    public void registerFailed(Context c) {

  106.        throw new RuntimeException("INVALID_OPERATE_ERROR");

  107.    }

  108.    @Override

  109.    public void unRegister(Context c) {

  110.        c.setState(new UnconnectState());

  111.    }

  112.    @Override

  113.    public String getCurState() {

  114.        return StateEnum.REGISTED.toString();

  115.    }

  116. }

注意的是,如果某个行为不会触发状态的变化,我们可以抛出一个 RuntimeException 异常。此外,调用时,通过环境类控制状态的切换,如下所示。

 
  1. public class Client {

  2.    public static void main(String[] args) {

  3.        Context context = new Context();

  4.        context.connect();

  5.        context.register();

  6.    }

  7. }

Spring StateMachine 让状态机结构更加层次化,可以帮助开发者简化状态机的开发过程。现在,我们来用 Spring StateMachine 进行改造。修改 pom 文件,添加 Maven 依赖。

 
  1. <dependency>

  2.    <groupId>org.springframework.statemachine</groupId>

  3.    <artifactId>spring-statemachine-core</artifactId>

  4.    <version>1.2.0.RELEASE</version>

  5. </dependency>

定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 4 种状态。

 
  1. public enum RegStatusEnum {

  2.    // 未连接

  3.    UNCONNECTED,

  4.    // 已连接

  5.    CONNECTED,

  6.    // 注册中

  7.    REGISTERING,

  8.    // 已注册

  9.    REGISTERED;

  10. }

定义一个行为枚举,其中包括连接、注册、注册成功、注册失败、注销 5 种行为事件。

 
  1. public enum RegEventEnum {

  2.    // 连接

  3.    CONNECT,

  4.    // 注册

  5.    REGISTER,

  6.    // 注册成功

  7.    REGISTER_SUCCESS,

  8.    // 注册失败

  9.    REGISTER_FAILED,

  10.    // 注销

  11.    UN_REGISTER;

  12. }

接着,我们需要进行状态机配置,其中 @EnableStateMachine 注解,标识启用 Spring StateMachine 状态机功能。

 
  1. @Configuration

  2. @EnableStateMachine

  3. public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {

  4. }

我们需要初始化状态机的状态。其中,initial(RegStatusEnum.UNCONNECTED) 定义了初始状态是未连接状态。states(EnumSet.allOf(RegStatusEnum.class)) 定义了状态机中存在的所有状态。

 
  1. @Override

  2. public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {

  3.    states.withStates()

  4.    // 定义初始状态

  5.    .initial(RegStatusEnum.UNCONNECTED)

  6.    // 定义状态机状态

  7.    .states(EnumSet.allOf(RegStatusEnum.class));

  8. }

我们需要初始化当前状态机有哪些状态事件。其中, source 指定原始状态,target 指定目标状态,event 指定触发事件。

 
  1. @Override

  2. public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions)

  3.        throws Exception {

  4.        // 1.连接事件

  5.        // 未连接 -> 已连接

  6.        .withExternal()

  7.            .source(RegStatusEnum.UNCONNECTED)

  8.            .target(RegStatusEnum.CONNECTED)

  9.            .event(RegEventEnum.CONNECT)

  10.        .and()

  11.        .withExternal()

  12.            .source(RegStatusEnum.CONNECTED)

  13.            .target(RegStatusEnum.CONNECTED)

  14.            .event(RegEventEnum.CONNECT)

  15.        .and()                    

  16.  

  17.        // 2.注册事件  

  18.        // 已连接 -> 注册中

  19.        .withExternal()

  20.            .source(RegStatusEnum.CONNECTED)

  21.            .target(RegStatusEnum.REGISTERING)

  22.            .event(RegEventEnum.REGISTER)

  23.        .and()

  24.       .withExternal()

  25.            .source(RegStatusEnum.REGISTERING)

  26.            .target(RegStatusEnum.REGISTERING)

  27.            .event(RegEventEnum.REGISTER)

  28.        .and()

  29.  

  30.        // 3.注册成功事件  

  31.        // 注册中 -> 已注册

  32.        .withExternal()

  33.            .source(RegStatusEnum.REGISTERING)

  34.            .target(RegStatusEnum.REGISTERED)

  35.            .event(RegEventEnum.REGISTER_SUCCESS)

  36.        .and()

  37.        .withExternal()

  38.            .source(RegStatusEnum.REGISTERED)

  39.            .target(RegStatusEnum.REGISTERED)

  40.            .event(RegEventEnum.REGISTER_SUCCESS)

  41.        .and()

  42.  

  43.         // 4.注册失败事件  

  44.        // 注册中 -> 未连接

  45.        .withExternal()

  46.            .source(RegStatusEnum.REGISTERING)

  47.            .target(RegStatusEnum.UNCONNECTED)

  48.            .event(RegEventEnum.REGISTER_FAILED)

  49.        .and()

  50.  

  51.        // 5.注销事件

  52.        // 已连接 -> 未连接

  53.        .withExternal()

  54.            .source(RegStatusEnum.CONNECTED)

  55.            .target(RegStatusEnum.UNCONNECTED)

  56.            .event(RegEventEnum.UN_REGISTER)

  57.        .and()

  58.        // 注册中 -> 未连接

  59.        .withExternal()

  60.            .source(RegStatusEnum.REGISTERING)

  61.            .target(RegStatusEnum.UNCONNECTED)

  62.            .event(RegEventEnum.UN_REGISTER)

  63.        .and()

  64.        // 已注册 -> 未连接

  65.        .withExternal()

  66.            .source(RegStatusEnum.REGISTERED)

  67.            .target(RegStatusEnum.UNCONNECTED)

  68.            .event(RegEventEnum.UN_REGISTER)

  69.        ;

  70. }

Spring StateMachine 提供了注解配置实现方式,所有 StateMachineListener 接口中定义的事件都能通过注解的方式来进行配置实现。这里以连接事件为案例,@OnTransition 中 source 指定原始状态,target 指定目标状态,当事件触发时将会被监听到从而调用 connect() 方法。

 
  1. @WithStateMachine

  2. public class StateMachineEventConfig {

  3.  

  4.    @OnTransition(source = "UNCONNECTED", target = "CONNECTED")

  5.    public void connect() {

  6.        System.out.println("///");

  7.        System.out.println("连接事件, 未连接 -> 已连接");

  8.        System.out.println("///");

  9.    }

  10.  

  11.    @OnTransition(source = "CONNECTED", target = "REGISTERING")

  12.    public void register() {

  13.        System.out.println("///");

  14.        System.out.println("注册事件, 已连接 -> 注册中");

  15.        System.out.println("///");

  16.    }

  17.  

  18.    @OnTransition(source = "REGISTERING", target = "REGISTERED")

  19.    public void registerSuccess() {

  20.        System.out.println("///");

  21.        System.out.println("注册成功事件, 注册中 -> 已注册");

  22.        System.out.println("///");

  23.    }

  24.  

  25.    @OnTransition(source = "REGISTERED", target = "UNCONNECTED")

  26.    public void unRegister() {

  27.        System.out.println("///");

  28.        System.out.println("注销事件, 已注册 -> 未连接");

  29.        System.out.println("///");

  30.    }

  31. }

Spring StateMachine 让状态机结构更加层次化,我们来回顾下几个核心步骤:第一步,定义状态枚举。第二步,定义事件枚举。第三步,定义状态机配置,设置初始状态,以及状态与事件之间的关系。第四步,定义状态监听器,当状态变更时,触发方法。