SpringBoot-Statemachine实现状态机持久化

状态机之所以强大,是因为始终保证行为是一致的,这使得调试相对容易。这是因为在机器启动时,操作规则是不可更改的。其思想是,应用程序可能存在有限数量的状态,某些预定义的触发器可以将应用程序从一种状态转移到另一种状态。这样的触发器可以基于事件或计时器。

在应用程序之外定义高级逻辑,然后依靠状态机来管理状态要容易得多。您可以通过发送事件、侦听更改或请求当前状态来与状态机交互。

依赖

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

状态事件

状态,示例:创建–>执行中–>结束

import lombok.extern.slf4j.Slf4j;

/**
 * Description:状态枚举
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@Slf4j
public enum StatusEnum {

    /**
     * 创建
     */
    STATUS_CREATE("100", "创建"),

    /**
     * 执行中
     */
    STATUS_RUNNING("200", "执行中"),

    /**
     * 结束
     */
    STATUS_FINISH("300", "结束");

    private String value;
    private String desc;

    StatusEnum(String value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    public static String getDescByCode(String code) {
        StatusEnum[] values = StatusEnum.values();
        for (StatusEnum value : values) {
            if (value.getValue().equals(code)) {
                return value.getDesc();
            }
        }
        return null;
    }

    public static StatusEnum getWaveStatusByValue(String value) {
        for (StatusEnum statusEnum : StatusEnum.values()) {
            if (statusEnum.getValue().equals(value)) {
                return statusEnum;
            }
        }
        return null;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

事件,示例:创建–>执行中–>结束

/**
 * Description:事件枚举
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
public enum EventEnum {

    CREATE("创建"),
    RUNNING("执行中"),
    FINISH("结束");

    private String desc;

    EventEnum(String desc) {
        this.desc = desc;
    }
}

常量工具类:

import java.util.HashMap;
import java.util.Set;

/**
 * Description:常量
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
public class Constants {

    //目标状态和事件的对应关系
    public static HashMap<String, EventEnum> statusEventMap = Maps.newHashMap();

    static {
        statusEventMap.put(StatusEnum.STATUS_CREATE.getValue(), EventEnum.RUNNING);
        statusEventMap.put(StatusEnum.STATUS_RUNNING.getValue(), EventEnum.FINISH);
    }

}

数据库对象

基础表,抽取用于引用,头表和明细表,都包含状态status

import lombok.Data;

@Data
public class BaseVO {

    private String id;

    private String status;

}
public class HeaderVO extends BaseVO{
}
public class DetailVO extends BaseVO{
}

状态机持久化

头表持久化逻辑:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Component;

/**
 * Description:状态持久化,如持久到数据库
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@Slf4j
@Component
public class HeaderStateMachinePersist implements StateMachinePersist<StatusEnum, EventEnum, HeaderVO> {

    @Autowired
    private HeaderService headerService;

    @Override
    public void write(StateMachineContext<StatusEnum, EventEnum> stateMachineContext, HeaderVO header)
            throws Exception {
        String oldWaveStatus = header.getStatus();
        String targetStatus = stateMachineContext.getState().getValue();
        header.setStatus(targetStatus);
        headerService.updateHeaderStatus(header);
        // header表操作
        // ...
        log.info("header id={},oldStatus={},targetStatus={}", header.getId(), oldWaveStatus, targetStatus);
        // 日志记录
        // ..
    }

    @Override
    public StateMachineContext<StatusEnum, EventEnum> read(HeaderVO header) throws Exception {
        log.info("header id={},oldStatus={}", header.getId(), header.getStatus());
        return new DefaultStateMachineContext<>(StatusEnum.getWaveStatusByValue(header.getStatus()), null, null,
                null, null);
    }
}

明细表持久化逻辑:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Component;

/**
 * Description:状态持久化,如持久到数据库
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@Slf4j
@Component
public class DetailStateMachinePersist implements StateMachinePersist<StatusEnum, EventEnum, DetailVO> {

    @Autowired
    private DetailService detailService;

    @Override
    public void write(StateMachineContext<StatusEnum, EventEnum> stateMachineContext, DetailVO detail)
            throws Exception {
        String oldWaveStatus = detail.getStatus();
        String targetStatus = stateMachineContext.getState().getValue();
        detail.setStatus(targetStatus);
        detailService.updateDetailStatus(detail);
        // detail表操作
        // ...
        log.info("detail id={},oldStatus={},targetStatus={}", detail.getId(), oldWaveStatus, targetStatus);
        // 日志记录
        // ..
    }

    @Override
    public StateMachineContext<StatusEnum, EventEnum> read(DetailVO detail) throws Exception {
        log.info("detail id={},oldStatus={}", detail.getId(), detail.getStatus());
        return new DefaultStateMachineContext<>(StatusEnum.getWaveStatusByValue(detail.getStatus()), null, null,
                null, null);
    }
}

事件触发入口

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.persist.StateMachinePersister;

import java.util.UUID;

/**
 * Description:持久化入口
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@Slf4j
@Data
public class StatemachineService<S, E, T extends BaseVO> {

    private StateMachinePersister<S, E, T> stateMachinePersist;
    private StateMachineFactory<S, E> stateMachineFactory;
    private String headerName;

    public boolean execute(T data, E event) throws Exception {
        // 利用随记ID创建状态机,创建时没有与具体定义状态机绑定
        StateMachine<S, E> stateMachine = stateMachineFactory.getStateMachine(UUID.randomUUID());
        stateMachine.start();
        boolean success = false;
        try {
            stateMachinePersist.restore(stateMachine, data);
            // 发送事件,返回是否执行成功
            success = stateMachine.sendEvent(event);
            if (success) {
                log.info("persist begin!id={},event={}", data.getId(), event);
                // 持久化
                stateMachinePersist.persist(stateMachine, data);
                log.info("persist end!id={},event={}", data.getId(), event);
            } else {
                log.info("id={},event={},send Event fail", data.getId(), event);
            }
        } catch (Exception e) {
            log.error("id={},event={},state machine execute error", data.getId(), event, e);
            throw e;
        } finally {
            stateMachine.stop();
        }
        return success;
    }
}

构造器,创建不同状态机对象(头表、明细表):

import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.persist.StateMachinePersister;

import java.util.Objects;

/**
 * Description:状态机builder
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
public class StatemachineServiceBuilder<S, E, T extends BaseVO> {

    private StateMachinePersister<S, E, T> stateMachinePersist;
    private StateMachineFactory<S, E> stateMachineFactory;

    public StatemachineServiceBuilder<S, E, T>
    setStateMachinePersister(StateMachinePersister<S, E, T> stateMachinePersist) {
        this.stateMachinePersist = stateMachinePersist;
        return this;
    }

    public StatemachineServiceBuilder<S, E, T> setStateMachineFactory(StateMachineFactory<S, E> stateMachineFactory) {
        this.stateMachineFactory = stateMachineFactory;
        return this;
    }

    public StatemachineService<S, E, T> builder() {
        Objects.requireNonNull(stateMachineFactory, "stateMachineFactory 不能为空");
        Objects.requireNonNull(stateMachinePersist, "stateMachinePersist 不能");
        StatemachineService<S, E, T> service = new StatemachineService<>();
        service.setStateMachineFactory(stateMachineFactory);
        service.setStateMachinePersist(stateMachinePersist);
        return service;
    }

}

配置

状态机配置:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;

import java.util.EnumSet;

/**
 * Description:状态机配置
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@Configuration
@EnableStateMachineFactory
public class StatemachineConfigurer extends EnumStateMachineConfigurerAdapter<StatusEnum, EventEnum> {

    @Autowired
    private HeaderStateMachinePersist headerStateMachinePersist;

    @Autowired
    private DetailStateMachinePersist detailStateMachinePersist;

    @Autowired
    private StateMachineFactory<StatusEnum, EventEnum> stateMachineFactory;

    // 定义头表状态机入口bean
    @Bean
    public StatemachineService<StatusEnum, EventEnum, HeaderVO> headerStatemachineService() {
        return new StatemachineServiceBuilder<StatusEnum, EventEnum, HeaderVO>()
                .setStateMachineFactory(stateMachineFactory)
                .setStateMachinePersister(new DefaultStateMachinePersister<>(headerStateMachinePersist)).builder();
    }

    // 定义明细表状态机入口bean
    @Bean
    public StatemachineService<StatusEnum, EventEnum, DetailVO> detailStatemachineService() {
        return new StatemachineServiceBuilder<StatusEnum, EventEnum, DetailVO>()
                .setStateMachineFactory(stateMachineFactory)
                .setStateMachinePersister(new DefaultStateMachinePersister<>(detailStateMachinePersist)).builder();
    }

    @Override
    public void configure(StateMachineStateConfigurer<StatusEnum, EventEnum> states) throws Exception {
        // 起始状态
        states.withStates()
                .initial(StatusEnum.STATUS_CREATE)
                .states(EnumSet.allOf(StatusEnum.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<StatusEnum, EventEnum> transitions) throws Exception {
        // 状态与事件的关系
        transitions.withExternal()
                // 从当前状态到目标状态
                .source(StatusEnum.STATUS_CREATE).target(StatusEnum.STATUS_RUNNING)
                .event(EventEnum.RUNNING)
                .and().withExternal().source(StatusEnum.STATUS_RUNNING).target(StatusEnum.STATUS_FINISH)
                .event(EventEnum.FINISH);
    }
}

使用

@Service
public class ProcessService {

    @Autowired
    private StatemachineService<StatusEnum, EventEnum, HeaderVO> headerStatemachineService;

    @Autowired
    private StatemachineService<StatusEnum, EventEnum, DetailVO> detailStatemachineService;

    private void process() throws Exception {
        // 业务中从当前状态处理完--》新的状态,触发事件,自动持久化更新+处理额外事项
        HeaderVO headerVO = new HeaderVO();
        headerVO.setStatus(StatusEnum.STATUS_CREATE.getValue());
        headerStatemachineService.execute(headerVO, Constants.statusEventMap.get(StatusEnum.STATUS_CREATE.getValue()));

        DetailVO detailVO = new DetailVO();
        headerVO.setStatus(StatusEnum.STATUS_RUNNING.getValue());
        detailStatemachineService.execute(detailVO, Constants.statusEventMap.get(StatusEnum.STATUS_RUNNING.getValue()));
    }

}

监听器方式

另一种持久化方式,监听器,当触发事件后,相应状态变化,则触发对应的方法执行

import lombok.extern.slf4j.Slf4j;
import org.springframework.statemachine.annotation.*;

/**
 * Description:该配置实现了com.didispace.StateMachineConfig类中定义的状态机监听器实现。
 * 监听的方式触发事件进行持久化
 * @author W
 * @version 1.0.0
 * @date 2022/9/29
 */
@WithStateMachine
@Slf4j
public class EventConfig {

    @OnTransition(target = "STATUS_CREATE")
    public void create() {
        log.info("创建...");
        // 持久化
    }

    @OnTransition(source = "STATUS_CREATE", target = "STATUS_RUNNING")
    public void running() {
        log.info("运行中...");
        // 持久化
    }

    @OnTransitionStart(source = "STATUS_RUNNING", target = "STATUS_FINISH")
    public void finish() {
        log.info("结束...");
        // 持久化
    }
}

触发:

@Service
public class ProcessService {

    @Autowired
    private StateMachine<StatusEnum, EventEnum> stateMachine;

    private void process() throws Exception {
        stateMachine.start();    //start()创建状态,起始
        stateMachine.sendEvent(EventEnum.RUNNING);    //调用running方法
        stateMachine.sendEvent(Events.FINISH);  //调用用finish方法
    }
}