状态机实战

170 阅读9分钟

需求背景与状态机

在实习过程中, 有一种ETL任务是这样的, 将sql查询出来的结果集导出到某种数据库中, 其中涉及到了sql版本管理与上下线的需求. 用户可以编辑sql, 保存不同的sql版本, 可以上线或者下线某个的sql版本, 上线时还要进行审批. 那么这个sql版本就要在上线, 下线, 审批中, 审批不通过等等状态中进行流转.

如果上线没有审批时, 状态转换就是这样的. image.png

如果上线需要经过审批, 那么状态转换就是这样的. image.png

机智如我, 立刻就想到了一个设计模式: 状态模式. 紧接着, 就涉及到了另一个概念: 有限状态机.

不多bb, 状态机的概念自行百度. 下面来说状态机的4个重要组成部分

  • State,状态: 草稿, 已上线, 已下线这就是sql版本的状态
  • Event,事件: 事件就是执行某个操作的触发条件. 比如, 从草稿到审批中需要一个上线申请这样一个事件
  • Action,动作: 事件发生以后要执行动作. 上线申请这个事件需要做这么几件事, 发起bpm审批流申请上线, 修改sql版本的状态为审批中, 这个就是动作.
  • Transition,变换: 从一个状态变化为另一个状态. 比如从操作变到审批中就是一个状态变换.

接下来, 用2种方式来实现一下状态机. 分别是状态模式, 状态集合实现状态机. 然后是spring状态机的使用.

状态模式实现状态机

定义状态枚举

public enum StateEnum {
    TEMP(0, new TempState()),
    ONLINE(1, new OnlineState()),
    PROCESSING(2, new ProcessingState()),
    REJECTED(3, new RejectedState()),
    OFFLINE(4, new OfflineState()),
    ;
    private final int code;
    private final State state;

    StateEnum(int code, State state) {
        this.code = code;
        this.state = state;
    }

    public int getCode() {
        return code;
    }

    public State getState() {
        return state;
    }

    @Override
    public String toString() {
        return "StateEnum{" +
                "state=" + state +
                '}';
    }
}

定义事件枚举

public enum Event {
    ONLINE_WITHOUT_APPLY, // 从 草稿/已下线 变成已上线
    ONLINE_APPLY,  // 从 草稿/已上线 变成审批中
    PUBLISH,  // 从审批中变成已上线
    REJECT,  // 从审批中变成审批不通过
    CANCEL_PUBLISH;  // 从已上线变为已下线
}

定义抽象状态父类

public abstract class State {
    // 状态机
    private StateMachine stateMachine;

    public StateMachine getStateMachine() {
        return stateMachine;
    }

    public void setStateMachine(StateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }

    // 上线不经过审批
    public abstract void onlineWithoutApply();

    // 上线审批
    public abstract void onlineApply();

    // 审批不通过
    public abstract void reject();

    // 发布 (审批中->已上线)
    public abstract void publish();

    // 取消发布 (已上线->已下线)
    public abstract void cancelPublish();
}

定义具体的状态类

// 草稿
public class TempState extends State {
    @Override
    public void onlineWithoutApply() {
        getStateMachine().online();
    }

    @Override
    public void onlineApply() {
        getStateMachine().apply();
    }

    @Override
    public void reject() {
        throw new RuntimeException("in valid: TempState -> reject");
    }

    @Override
    public void publish() {
        throw new RuntimeException("in valid: TempState -> publish");
    }

    @Override
    public void cancelPublish() {
        throw new RuntimeException("in valid: TempState -> cancelPublish");
    }
}


// 已上线
public class OnlineState extends State {
    @Override
    public void onlineWithoutApply() {
        throw new RuntimeException("in valid: OnlineState -> onlineWithoutApply");
    }

    @Override
    public void onlineApply() {
        throw new RuntimeException("in valid: OnlineState -> onlineApply");
    }

    @Override
    public void reject() {
        throw new RuntimeException("in valid: OnlineState -> reject");
    }

    @Override
    public void publish() {
        throw new RuntimeException("in valid: OnlineState -> publish");
    }

    @Override
    public void cancelPublish() {
        getStateMachine().offline();
    }
}


// 审批中
public class ProcessingState extends State {
    @Override
    public void onlineWithoutApply() {
        throw new RuntimeException("in valid: ProcessingState -> onlineWithoutApply");
    }

    @Override
    public void onlineApply() {
        throw new RuntimeException("in valid: ProcessingState -> onlineApply");
    }

    @Override
    public void reject() {
        getStateMachine().reject();
    }

    @Override
    public void publish() {
        getStateMachine().online();
    }

    @Override
    public void cancelPublish() {
        throw new RuntimeException("in valid: ProcessingState -> cancelPublish");
    }
}


// 已下线
public class OfflineState extends State {
    @Override
    public void onlineWithoutApply() {
        getStateMachine().online();
    }

    @Override
    public void onlineApply() {
        getStateMachine().apply();
    }

    @Override
    public void reject() {
        throw new RuntimeException("in valid: OfflineState -> reject");
    }

    @Override
    public void publish() {
        throw new RuntimeException("in valid: OfflineState -> publish");
    }

    @Override
    public void cancelPublish() {
        throw new RuntimeException("in valid: OfflineState -> cancelPublish");
    }
}


// 审批不通过
public class RejectedState extends State {
    @Override
    public void onlineWithoutApply() {
        throw new RuntimeException("in valid: RejectedState -> onlineWithoutApply");
    }

    @Override
    public void onlineApply() {
        throw new RuntimeException("in valid: RejectedState -> onlineApply");
    }

    @Override
    public void reject() {
        throw new RuntimeException("in valid: RejectedState -> reject");
    }

    @Override
    public void publish() {
        throw new RuntimeException("in valid: RejectedState -> publish");
    }

    @Override
    public void cancelPublish() {
        throw new RuntimeException("in valid: RejectedState -> cancelPublish");
    }
}

定义状态机类

public class StateMachine {
    // 当前状态
    private State curState;

    public StateMachine(State curState) {
        setCurState(curState);
    }

    // 传入不同的事件, 执行不同状态的动作
    public void execute(Event event) {
        switch (event) {
            case ONLINE_WITHOUT_APPLY:
                getCurState().onlineWithoutApply();
                break;
            case ONLINE_APPLY:
                getCurState().onlineApply();
                break;
            case PUBLISH:
                getCurState().publish();
                break;
            case REJECT:
                getCurState().reject();
                break;
            case CANCEL_PUBLISH:
                getCurState().cancelPublish();
                break;
            default:
                throw new RuntimeException("in valid event: " + event);
        }
    }

    // 以下的4个属于action
    public void online() {
        System.out.println("执行sql版本上线相关的操作");
        System.out.println("修改sql版本为已上线");
        setCurState(StateEnum.ONLINE.getState());
    }

    public void apply() {
        System.out.println("发起上线审批, 执行sql版本审批相关的操作");
        System.out.println("修改状态为审批中");
        setCurState(StateEnum.PROCESSING.getState());
    }

    public void reject() {
        System.out.println("执行sql版本审批不通过相关的操作");
        System.out.println("设置sql版本状态为审批不通过");
        setCurState(StateEnum.REJECTED.getState());
    }

    public void offline() {
        System.out.println("执行sql版本已下线相关的操作");
        System.out.println("修改sql版本状态为已下线");
        setCurState(StateEnum.OFFLINE.getState());
    }

    public State getCurState() {
        return curState;
    }

    public void setCurState(State curState) {
        this.curState = curState;
        curState.setStateMachine(this);
    }
}

简单测试使用

@Test
void test() {
    StateMachine stateMachine = new StateMachine(StateEnum.TEMP.getState());
    stateMachine.execute(Event.ONLINE_APPLY);
    stateMachine.execute(Event.PUBLISH);
    stateMachine.execute(Event.CANCEL_PUBLISH);
    stateMachine.execute(Event.ONLINE_WITHOUT_APPLY);
}

状态模式实现状态机并不太好, 虽然去掉了大量switch case或者if else的状态, 但是不完全符合开闭原则, 新增或者删除状态需要修改状态机代码, 新增事件也需要修改很多代码.

状态集合实现状态机

定义状态枚举

public enum State {
    TEMP(0, "草稿"),
    ONLINE(1, "已上线"),
    PROCESSING(2, "审批中"),
    REJECTED(3, "审批不通过"),
    OFFLINE(4, "已下线");

    private final int code;
    private final String desc;

    State(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "State{" +
                "code=" + code +
                ", desc='" + desc + '\'' +
                '}';
    }
}

定义事件

public enum Event {
    ONLINE_WITHOUT_APPLY, // 从 草稿/已上线 变成已上线
    ONLINE_APPLY,  // 从 草稿/已上线 变成审批中
    PUBLISH,  // 从审批中变成已上线
    REJECT,  // 从审批中变成审批不通过
    CANCEL_PUBLISH;  // 从已上线变为已下线
}

定义每个事件需要做的动作

public interface Action {
    void execute();
}

定义变换

@Data
@Builder
public class Transition {
    private State curState;
    private State nextState;
    private Event event;
    private Action action;
}

动作实现类

public class OnlineWithoutApplyAction implements Action{
    @Override
    public void execute() {
        System.out.println("执行sql版本上线相关的操作");
        System.out.println("修改sql版本为已上线");
    }
}


public class OnlineApplyAction implements Action {
    @Override
    public void execute() {
        System.out.println("发起上线审批, 执行sql版本审批相关的操作");
        System.out.println("修改状态为审批中");
    }
}


public class RejectAction implements Action {
    @Override
    public void execute() {
        System.out.println("执行sql版本审批不通过相关的操作");
        System.out.println("设置sql版本状态为审批不通过");
    }
}


public class CancelPublishAction implements Action {
    @Override
    public void execute() {
        System.out.println("执行sql版本下线相关的操作");
        System.out.println("修改sql版本状态为已下线");
    }
}

状态机类

public class StateMachine {
    private final List<Transition> transitionList = Stream.of(
            // 草稿状态到上线
            Transition.builder()
                    .curState(State.TEMP)
                    .nextState(State.ONLINE)
                    .event(Event.ONLINE_WITHOUT_APPLY)
                    .action(new OnlineWithoutApplyAction())
                    .build(),
            // 已下线状态到上线
            Transition.builder()
                    .curState(State.OFFLINE)
                    .nextState(State.ONLINE)
                    .event(Event.ONLINE_WITHOUT_APPLY)
                    .action(new OnlineWithoutApplyAction())
                    .build(),
            // 草稿状态到审批中
            Transition.builder()
                    .curState(State.TEMP)
                    .nextState(State.PROCESSING)
                    .event(Event.ONLINE_APPLY)
                    .action(new OnlineApplyAction())
                    .build(),
            // 已下线状态到审批中
            Transition.builder()
                    .curState(State.OFFLINE)
                    .nextState(State.PROCESSING)
                    .event(Event.ONLINE_APPLY)
                    .action(new OnlineApplyAction())
                    .build(),
            // 审批中到已上线
            Transition.builder()
                    .curState(State.PROCESSING)
                    .nextState(State.ONLINE)
                    .event(Event.PUBLISH)
                    // 这里可以复用OnlineWithoutApplyAction()的逻辑
                    .action(new OnlineWithoutApplyAction())
                    .build(),
            // 从审批中变成审批不通过
            Transition.builder()
                    .curState(State.PROCESSING)
                    .nextState(State.REJECTED)
                    .event(Event.REJECT)
                    .action(new RejectAction())
                    .build(),
            // 从已上线变为已下线
            Transition.builder()
                    .curState(State.ONLINE)
                    .nextState(State.OFFLINE)
                    .event(Event.CANCEL_PUBLISH)
                    .action(new CancelPublishAction())
                    .build()
    ).collect(Collectors.toList());

    private State state;

    public StateMachine(State state) {
        this.state = state;
    }

    public void execute(Event event) {
        // 如果觉得这里次次遍历不太好, 可以用HashMap来存储, 以Event为key, 以Transition为value
        Transition transition = transitionList.stream()
                .filter(trans -> trans.getEvent().equals(event))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("in valid event" + event));
        
        this.state = transition.getNextState();
        transition.getAction().execute();
    }
}

使用状态机

@Test
void test() {
    StateMachine stateMachine = new StateMachine(State.TEMP);
    stateMachine.execute(Event.ONLINE_APPLY);
    stateMachine.execute(Event.PUBLISH);
    stateMachine.execute(Event.CANCEL_PUBLISH);
    stateMachine.execute(Event.ONLINE_WITHOUT_APPLY);
}

从这里可以看出, 使用状态集合实现状态机, 比较符合开闭原则, 而且集合中清楚的定义了从某状态到另一状态的触发事件与动作. 可读性强, 修改方便.

spring状态机

引入依赖

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

实体类

@Data
public class SyncTaskVersion {
    private Long id;
    private SyncTaskVersionStates versionStates;
    // ... 省略其他字段
}

状态枚举

public enum SyncTaskVersionStates {
    TEMP(0, "草稿"),
    APPLYING(1, "已上线"),
    PROCESSING(2, "审批中"),
    REJECTED(3, "审批不通过"),
    OFFLINE(4, "已下线");

    private final int code;
    private final String desc;

    SyncTaskVersionStates(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "SyncTaskVersionStates{" +
                "code=" + code +
                ", desc='" + desc + '\'' +
                '}';
        // return name();
    }
}

事件

public enum SyncTaskVersionEvents {
    ONLINE_WITHOUT_APPLY, // 从 草稿/已上线 变成已上线
    ONLINE_APPLY,  // 从 草稿/已上线 变成审批中
    PUBLISH,  // 从审批中变成已上线
    REJECT,  // 从审批中变成审批不通过
    CANCEL_PUBLISH;  // 从已上线变为已下线
}

状态机配置 注: @EnableStateMachine需要给状态机名字, 然后后续监听时@WithStateMachine需要指定状态机名字

@Configuration
@EnableStateMachine(name = "syncTaskStateMachine")
public class StateMachineConfig extends StateMachineConfigurerAdapter<SyncTaskVersionStates, SyncTaskVersionEvents> {

    /**
     * 配置状态
     */
    @Override
    public void configure(StateMachineStateConfigurer<SyncTaskVersionStates, SyncTaskVersionEvents> states) throws Exception {
        states.withStates()
                .initial(SyncTaskVersionStates.TEMP)
                .states(EnumSet.allOf(SyncTaskVersionStates.class));
    }

    /**
     * 配置状态转换与事件的关系
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<SyncTaskVersionStates, SyncTaskVersionEvents> transitions) throws Exception {
		transitions
            // 草稿状态到上线
            .withExternal()
            .source(SyncTaskVersionStates.TEMP)
            .target(SyncTaskVersionStates.ONLINE)
            .event(SyncTaskVersionEvents.ONLINE_WITHOUT_APPLY)
            .and()
            // 已下线状态到上线
            .withExternal()
            .source(SyncTaskVersionStates.OFFLINE)
            .target(SyncTaskVersionStates.ONLINE)
            .event(SyncTaskVersionEvents.ONLINE_WITHOUT_APPLY)
            .and()
            // 草稿状态到审批中
            .withExternal()
            .source(SyncTaskVersionStates.TEMP)
            .target(SyncTaskVersionStates.APPLYING)
            .event(SyncTaskVersionEvents.ONLINE_APPLY)
            .and()
            // 已下线状态到审批中
            .withExternal()
            .source(SyncTaskVersionStates.OFFLINE)
            .target(SyncTaskVersionStates.APPLYING)
            .event(SyncTaskVersionEvents.ONLINE_APPLY)
            .and()
            // 审批中到已上线
            .withExternal()
            .source(SyncTaskVersionStates.APPLYING)
            .target(SyncTaskVersionStates.ONLINE)
            .event(SyncTaskVersionEvents.PUBLISH)
            .and()
            // 从审批中变成审批不通过
            .withExternal()
            .source(SyncTaskVersionStates.APPLYING)
            .target(SyncTaskVersionStates.REJECTED)
            .event(SyncTaskVersionEvents.REJECT)
            .and()
            // 从已上线变为已下线
            .withExternal()
            .source(SyncTaskVersionStates.ONLINE)
            .target(SyncTaskVersionStates.OFFLINE)
            .event(SyncTaskVersionEvents.CANCEL_PUBLISH);
    }

    @Bean
    public StateMachinePersister<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion> persister() {
        return new DefaultStateMachinePersister<>(new StateMachinePersist<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion>() {
            @Override
            public void write(StateMachineContext<SyncTaskVersionStates, SyncTaskVersionEvents> context, SyncTaskVersion syncTaskVersion) throws Exception {
                //此处并没有进行持久化操作
                // 这里持久化一般是将StateMachineContext状态上下文保存到内存或者redis中, 下次读取在用, 但是实际上状态一般随着实体类保存在数据库表中, 并且状态可能会因为其他原因被修改, 所以实际开发中一般不进行持久化操作. 都是实时获取状态
            }

            @Override
            public StateMachineContext<SyncTaskVersionStates, SyncTaskVersionEvents> read(SyncTaskVersion syncTaskVersion) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext<>(syncTaskVersion.getVersionStates(), null, null, null);
            }
        });
    }
}

自定义@StatesOnTransition 注解, 里面用了@OnTransition , 所以source和target传入枚举类就能触发. 是spring官方推荐的方式.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {
    SyncTaskVersionStates[] source() default {};
    SyncTaskVersionStates[] target() default {};
}

监听

@Component
@WithStateMachine(name = "syncTaskStateMachine")
public class SyncTaskVersionListener {
    @StatesOnTransition(source = {SyncTaskVersionStates.TEMP, SyncTaskVersionStates.OFFLINE, SyncTaskVersionStates.APPLYING}, target = SyncTaskVersionStates.ONLINE)
    public void online(@EventHeaders Map<String, Object> headers) {
        SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
        System.out.println("执行sql版本上线相关的操作");
        System.out.println("修改sql版本为已上线");
    }

    @StatesOnTransition(source = {SyncTaskVersionStates.TEMP, SyncTaskVersionStates.OFFLINE}, target = SyncTaskVersionStates.APPLYING)
    public void apply(@EventHeaders Map<String, Object> headers) {
        SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
        System.out.println("发起上线审批, 执行sql版本审批相关的操作");
        System.out.println("修改状态为审批中");
    }

    @StatesOnTransition(source = SyncTaskVersionStates.APPLYING, target = SyncTaskVersionStates.REJECTED)
    public void reject(@EventHeaders Map<String, Object> headers) {
        SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
        System.out.println("执行sql版本审批不通过相关的操作");
        System.out.println("设置sql版本状态为审批不通过");
    }

    @StatesOnTransition(source = SyncTaskVersionStates.ONLINE, target = SyncTaskVersionStates.OFFLINE)
    public void offline(@EventHeaders Map<String, Object> headers) {
        SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
        System.out.println("执行sql版本已下线相关的操作");
        System.out.println("修改sql版本状态为已下线");
    }
}

sevice层来使用状态机

@Service
public class SyncTaskServiceImpl implements SyncTaskService {
    @Resource
    private StateMachine<SyncTaskVersionStates, SyncTaskVersionEvents> syncTaskStateMachine;
    @Resource
    private StateMachinePersister<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion> persister;

    @Override
    public void onlineApply(Long id) {
        // 假装从数据中获取SyncTaskVersion
        SyncTaskVersion syncTaskVersion = new SyncTaskVersion();
        syncTaskVersion.setId(id);
        syncTaskVersion.setVersionStates(SyncTaskVersionStates.APPLYING);

        boolean sendRes = sendEvent(SyncTaskVersionEvents.PUBLISH, syncTaskVersion);
        if (!sendRes) {
            throw new RuntimeException("上线审批启动失败");
        }
    }
    
    // 省略下线, 审批通过, 等方法

    // 发送事件的通用方法
    private synchronized boolean sendEvent(SyncTaskVersionEvents events, SyncTaskVersion syncTaskVersion) {
        boolean res = false;
        try {
            syncTaskStateMachine.start();
            persister.restore(syncTaskStateMachine, syncTaskVersion);
            Message<SyncTaskVersionEvents> message = MessageBuilder.withPayload(events).setHeader("syncTaskVersion", syncTaskVersion).build();
            res = syncTaskStateMachine.sendEvent(message);
            persister.persist(syncTaskStateMachine, syncTaskVersion);
        } catch (Exception e) {
            // 打印日志
            System.out.println(e.getMessage());
        } finally {
            syncTaskStateMachine.stop();
        }

        return res;
    }
}

代码详情见: github.com/RaisingPigs…