Java状态机实战一:接口定义

在复杂业务系统开发中,状态机是一种非常实用的设计模式,特别适用于需要管理状态流转的场景,如订单系统、工作流引擎等。本文将深入解析状态机的设计思想和实现细节,帮助大家更好地理解和应用状态机模式。

一、状态机基础概念

1.1 什么是状态机?

状态机(State Machine),全称为有限状态机(Finite State Machine, FSM),是一种数学模型,描述了系统在不同状态之间的转移规则。它由以下几个部分组成:

  • 状态(State):系统可能处于的某个具体情况。
  • 事件(Event)或输入(Input):触发状态变化的外部或内部因素。
  • 转换(Transition):状态如何根据事件变化。
  • 初始状态(Initial State):系统的起始状态。
  • 终止状态(Final State,可选):系统可能的结束状态。

1.2 状态机应用场景

  • 订单状态管理:未支付→已支付→已发货→已完成
  • 审批流程:待审批→审批中→已通过/已拒绝
  • 任务调度:等待→执行中→成功/失败
  • 设备状态:离线→在线→工作中→故障

二、实战

2.1 核心接口定义

  • 状态定义接口
/**
 * 状态抽象接口
 */
public interface StatusDefine {

    /**
     * 返回状态编号
     */
    Integer getStatus();

    /**
     * 返回状态描述
     */
    String getDesc();

    /**
     * 返回状态代码
     */
    String getCode();
}
  • 状态变更事件接口
/**
 * 状态变量事件抽象接口
 */

public interface StatusChangeEvent {

    /**
     * 原始状态
     */
    StatusDefine getSourceStatus();

    /**
     * 变更后的状态
     */
    StatusDefine getTargetStatus();

    /**
     * @return 返回事件描述
     */
    String getDesc();

    /**
     * @return 返回事件代码
     */
    String getCode();

}
  • 状态核心处理器接口
/**
 * 状态变化处理器
 * 说明:
 * 1、功能说明:状态变更时执行的业务逻辑
 * 2、接口实现类的bean名称规则为:状态机名称_状态变更事件名称
 * 例如:在实现类上添加 @Component("order_close_dispatching_order") order为状态机名称,close_dispatching_order为取消正常派单订单的事件名称
 *
 */
public interface StatusChangeHandler<T extends StateMachineSnapshot> {

    /**
     * 状态变化处理逻辑
     *
     * @param bizId 业务id
     * @param statusChangeEventEnum 状态变更事件
     * @param bizSnapshot 快照
     */
    void handler(String bizId,StatusChangeEvent statusChangeEventEnum,T bizSnapshot);
}

  • 状态快照基础类
/**
 * 状态快照基础类
 */
public abstract class StateMachineSnapshot {

    /**
     * 返回快照id
     */
    public abstract String getSnapshotId();
    
    /**
     * 返回快照状态
     */
    public abstract Integer getSnapshotStatus();
    
    /**
     * 设置快照id
     */
    public abstract void setSnapshotId(String snapshotId);
    
    /**
     * 设置快照状态
     */
    public abstract void setSnapshotStatus(Integer snapshotStatus);
}

2.2 核心实现

@Slf4j
public abstract class AbstractStateMachine<T extends StateMachineSnapshot> {

    /**
     * 状态机持久化程序
     */
    private final StateMachinePersister stateMachinePersister;

    /**
     * 业务快照服务层程序
     */
    private final BizSnapshotService bizSnapshotService;

    /**
     * redis处理程序
     */
    private RedisTemplate redisTemplate;

    /**
     * 初始化状态
     */
    private final StatusDefine initState;

    /**
     * 状态机名称
     */
    private final String name;

    /**
     * 构造方法
     *
     * @param stateMachinePersister 状态机持久化程序
     * @param bizSnapshotService    业务快照服务层程序
     * @param redisTemplate         redis处理程序
     */
    protected AbstractStateMachine(StateMachinePersister stateMachinePersister, BizSnapshotService bizSnapshotService, RedisTemplate redisTemplate) {
        this.stateMachinePersister = stateMachinePersister;
        this.bizSnapshotService = bizSnapshotService;
        this.redisTemplate = redisTemplate;
        this.initState = getInitState();
        this.name = getName();
    }

    /**
     * @return 返回状态机名称
     */
    protected abstract String getName();

    /**
     * 后处理方法,在更改状态机执行逻辑
     */
    protected abstract void postProcessor(T bizSnapshot);

    /**
     * @return 初始状态
     */
    protected abstract StatusDefine getInitState();

    /**
     * 状态机初始化,不保存快照
     *
     * @param bizId 业务id
     * @return 初始化状态代码
     */
    public String start(String bizId) {
        return start(null, bizId, initState, null);
    }

    /**
     * 启动状态机,并设置当前状态,不保存快照
     *
     * @param bizId        业务id
     * @param statusDefine 当前状态
     * @return 当前状态代码
     */
    public String start(String bizId, StatusDefine statusDefine) {
        return start(null, bizId, statusDefine, null);
    }

    /**
     * 启动状态机,并设置当前状态和保存业务快照,快照不分库
     *
     * @param bizId        业务id
     * @param statusDefine 当前状态
     * @param bizSnapshot  快照
     * @return 当前状态代码
     */
    public String start(String bizId, StatusDefine statusDefine, T bizSnapshot) {
        return start(null, bizId, statusDefine, bizSnapshot);
    }

    /**
     * 状态机初始化,并保存业务快照,快照分库分表
     *
     * @param dbShardId   分库键
     * @param bizId       业务id
     * @param bizSnapshot 业务快照
     * @return 初始化状态代码
     */
    public String start(Long dbShardId, String bizId, T bizSnapshot) {
        return start(dbShardId, bizId, initState, bizSnapshot);
    }

    /**
     * 启动状态机,并设置当前状态和保存业务快照,快照分库分表
     *
     * @param dbShardId    分库键
     * @param bizId        业务id
     * @param statusDefine 当前状态
     * @param bizSnapshot  快照
     * @return 当前状态代码
     */
    public String start(Long dbShardId, String bizId, StatusDefine statusDefine, T bizSnapshot) {

        //1.初始化状态机状态
        String currentState = stateMachinePersister.getCurrentState(name, bizId);
        if (ObjectUtil.isEmpty(currentState)) {
            stateMachinePersister.init(name, bizId, statusDefine);
        } else {
            throw new IllegalStateException("已存在状态,不可初始化");
        }

        //2.保存业务快照
        if (bizSnapshot == null) {
            bizSnapshot = ReflectUtil.newInstance(getSnapshotClass());
        }
        //设置快照id
        bizSnapshot.setSnapshotId(bizId);
        //设置快照状态
        bizSnapshot.setSnapshotStatus(statusDefine.getStatus());
        //快照转json
        String bizSnapshotString = JSONUtil.toJsonStr(bizSnapshot);
        if (ObjectUtil.isNotEmpty(bizSnapshot)) {
            bizSnapshotService.save(dbShardId, name, bizId, statusDefine, bizSnapshotString);
        }
        //执行后处理方法
        postProcessor(bizSnapshot);

        return statusDefine.getCode();
    }

    /**
     * 持久化删除
     *
     * @param bizId 业务id
     */
    public void clear(String bizId) {
        stateMachinePersister.clear(name, bizId);
    }

    /**
     * 获取当前状态
     *
     * @param bizId 业务id
     * @return 当前状态代码
     */
    public String getCurrentState(String bizId) {
        return stateMachinePersister.getCurrentState(name, bizId);
    }

    /**
     * 获取当前快照
     *
     * @param bizId 业务id
     * @return 快照信息
     */
    public String getCurrentSnapshot(String bizId) {
        //当前状态code
        String currentState = getCurrentState(bizId);
        return bizSnapshotService.findLastSnapshotByBizIdAndState(name, bizId, currentState);
    }

    /**
     * 根据状态查询业务快照
     *
     * @param bizId        业务id
     * @param statusDefine 状态
     * @return 业务快照
     */
    public String getSnapshotByStatus(String bizId, StatusDefine statusDefine) {
        String statusCode = statusDefine == null ? null : statusDefine.getCode();
        return bizSnapshotService.findLastSnapshotByBizIdAndState(name, bizId, statusCode);
    }

    /**
     * 获取当前状态的快照缓存
     *
     * @param bizId 业务id
     * @return 快照信息
     */
    public String getCurrentSnapshotCache(String bizId) {
        //先查询缓存,如果缓存没有就查询数据库然后存缓存
        String key = "STATE_MACHINE:" + name + ":" + bizId;
        Object object = redisTemplate.opsForValue().get(key);
        if (ObjectUtil.isNotEmpty(object)) {
            return object.toString();
        }

        String bizSnapshot = getCurrentSnapshot(bizId);
        redisTemplate.opsForValue().set(key, bizSnapshot, 30, TimeUnit.MINUTES);
        return bizSnapshot;
    }

    /**
     * 新增快照
     *
     * @param dbShardId    分库键
     * @param bizId        业务id
     * @param statusDefine 状态
     * @param bizSnapshot  业务快照
     */
    public void saveSnapshot(Long dbShardId, String bizId, StatusDefine statusDefine, T bizSnapshot) {
        //快照转json
        String jsonString = JSONUtil.toJsonStr(bizSnapshot);
        //新增快照
        bizSnapshotService.save(dbShardId, name, bizId, statusDefine, jsonString);

        //清理缓存
        String key = "STATE_MACHINE:" + name + ":" + bizId;
        redisTemplate.delete(key);
    }

    /**
     * 变更状态并保存快照,快照不进行分库
     *
     * @param bizId                 业务id
     * @param statusChangeEventEnum 状态变换事件
     */
    public void changeStatus(String bizId, StatusChangeEvent statusChangeEventEnum) {
        changeStatus(null, bizId, statusChangeEventEnum, null);
    }

    /**
     * 变更状态并保存快照,快照不进行分库
     *
     * @param bizId                 业务id
     * @param statusChangeEventEnum 状态变换事件
     * @param bizSnapshot           业务数据快照(json格式)
     */
    public void changeStatus(String bizId, StatusChangeEvent statusChangeEventEnum, T bizSnapshot) {
        changeStatus(null, bizId, statusChangeEventEnum, bizSnapshot);
    }

    /**
     * 变更状态并保存快照,快照不进行分库
     *
     * @param dbShardId             分库键
     * @param bizId                 业务id
     * @param statusChangeEventEnum 状态变换事件
     */
    public void changeStatus(Long dbShardId, String bizId, StatusChangeEvent statusChangeEventEnum) {
        changeStatus(dbShardId, bizId, statusChangeEventEnum, null);
    }

    /**
     * 变更状态并保存快照,快照分库分表
     *
     * @param dbShardId             分库键
     * @param bizId                 业务id
     * @param statusChangeEventEnum 状态变换事件
     * @param bizSnapshot           业务数据快照(json格式)
     */
    public void changeStatus(Long dbShardId, String bizId, StatusChangeEvent statusChangeEventEnum, T bizSnapshot) {
        //1.查询当前状态
        String statusCode = getCurrentState(bizId);

        //2.校验起止状态是否与事件匹配
        if (ObjectUtil.isNotEmpty(statusChangeEventEnum.getSourceStatus()) && ObjectUtil.notEqual(statusChangeEventEnum.getSourceStatus().getCode(), statusCode)) {
            throw new CommonException(HTTP_INTERNAL_ERROR, "状态机起止状态与事件不匹配");
        }

        //3.获取状态处理程序bean
        //事件代码
        String eventCode = statusChangeEventEnum.getCode();
        StatusChangeHandler bean = null;
        try {
            bean = SpringUtil.getBean(name + "_" + eventCode, StatusChangeHandler.class);
        } catch (Exception e) {
            log.info("不存在‘{}’StatusChangeHandler", name + "_" + eventCode);
        }
        if (bizSnapshot == null) {
            bizSnapshot = ReflectUtil.newInstance(getSnapshotClass());
        }
        //设置快照id
        bizSnapshot.setSnapshotId(bizId);
        //设置目标状态
        bizSnapshot.setSnapshotStatus(statusChangeEventEnum.getTargetStatus().getStatus());
        if (ObjectUtil.isNotNull(bean)) {
            //4.执行状态变更
            bean.handler(bizId, statusChangeEventEnum, bizSnapshot);
        }

        //5.状态持久化
        stateMachinePersister.persist(name, bizId, statusChangeEventEnum.getTargetStatus());

        //6、存储快照
        if (ObjectUtil.isNotEmpty(bizSnapshot)) {
            //构建新的快照信息
            bizSnapshot = buildNewSnapshot(bizId, bizSnapshot, statusChangeEventEnum.getSourceStatus());
            String newBizSnapShotString = JSONUtil.toJsonStr(bizSnapshot);
            bizSnapshotService.save(dbShardId, name, bizId, statusChangeEventEnum.getTargetStatus(), newBizSnapShotString);
        }

        //7.清理快照缓存
        String key = "STATE_MACHINE:" + name + ":" + bizId;
        redisTemplate.delete(key);

        //执行后处理方法
        postProcessor(bizSnapshot);
    }

    /**
     * 构建新的快照数据
     *
     * @param bizId        业务id
     * @param bizSnapshot  业务快照
     * @param statusDefine 状态
     * @return 业务快照(json格式)
     */
    public T buildNewSnapshot(String bizId, T bizSnapshot, StatusDefine statusDefine) {
        //1.获取上一个状态订单快照
        String currentSnapshot = getSnapshotByStatus(bizId, statusDefine);
        if (ObjectUtil.isEmpty(currentSnapshot)) {
            return bizSnapshot;
        }

        //2.将当前状态订单快照转为bean
        T oldOrderSnapshotDTO = JSONUtil.toBean(currentSnapshot, getSnapshotClass());

        //3.将新的订单快照数据覆盖旧订单快照数据,忽略null
        T orderSnapshotDTO = BeanUtils.copyIgnoreNull(bizSnapshot, oldOrderSnapshotDTO, getSnapshotClass());
        return orderSnapshotDTO;
    }

    private Class<T> getSnapshotClass() {

        ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();

        return (Class<T>) parameterizedType.getActualTypeArguments()[0];

    }
}

2.3 状态持久化接口

/**
 * 业务数据快照
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("biz_snapshot")
public class BizSnapshot {

    /**
     * 主键
     */
    private Long id;

    /**
     * 状态机名称
     */
    private String stateMachineName;

    /**
     * 业务id
     */
    private String bizId;

    /**
     * 分库键
     */
    private Long dbShardId;

    /**
     * 状态,取StatusDefine中的 code
     */
    private String state;

    /**
     * 业务数据(json格式化)
     */
    private String bizData;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 创建时间
     */
    private LocalDateTime updateTime;
}

/**
 * 状态持久化
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("state_persister")
public class StatePersister {
    /**
     * 主键
     */
    private Long id;

    /**
     * 状态机名称
     */
    private String stateMachineName;

    /**
     * 业务id
     */
    private String bizId;

    /**
     * 状态,取StatusDefine中的 code
     */
    private String state;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}
  • 实现该接口将状态信息持久化到数据库
public interface StateMachinePersister {

    /**
     * 业务数据状态初始化
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param statusDefine     当前状态
     */
    void init(String stateMachineName, String bizId, StatusDefine statusDefine);

    /**
     * 业务数据状态持久化
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param statusDefine     当前状态
     */
    void persist(String stateMachineName, String bizId, StatusDefine statusDefine);

    /**
     * 查询业务数据当前持久化状态
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @return 当前持久化状态代码
     */
    String getCurrentState(String stateMachineName, String bizId);

    /**
     * 根据状态机名称和业务id清理持久化状态
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     */
    void clear(String stateMachineName, String bizId);
}

2.4 业务数据实现类

public interface BizSnapshotService {
    /**
     * 新增业务快照
     *
     * @param dbShardId        分库键
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param statusDefine     状态
     * @param bizSnapshot      业务快照
     */
    void save(Long dbShardId, String stateMachineName, String bizId, StatusDefine statusDefine, String bizSnapshot);

    /**
     * 根据业务id和状态查询最新业务快照
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param state            状态代码
     * @return 业务快照
     */
    String findLastSnapshotByBizIdAndState(String stateMachineName, String bizId, String state);
}

@Component
public class BizSnapshotServiceImpl implements BizSnapshotService {
    /**
     * 业务快照数据层处理程序
     */
    private final BizSnapshotMapper bizSnapshotMapper;

    /**
     * 默认分库键值,不进行分库时使用
     */
    private static final Long DEFAULT_DB_SHARD_ID = 1L;

    /**
     * 构造器
     *
     * @param bizSnapshotMapper 业务快照数据层处理程序
     */
    public BizSnapshotServiceImpl(BizSnapshotMapper bizSnapshotMapper) {
        this.bizSnapshotMapper = bizSnapshotMapper;
    }

    /**
     * 新增业务快照
     *
     * @param dbShardId        分库键
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param statusDefine     状态
     * @param bizSnapshot      业务快照
     */
    @Override
    public void save(Long dbShardId, String stateMachineName, String bizId, StatusDefine statusDefine, String bizSnapshot) {
        BizSnapshot model = BizSnapshot.builder()
                .id(IdUtil.getSnowflakeNextId())
                .stateMachineName(stateMachineName)
                .bizId(bizId)
                .dbShardId(dbShardId)
                .state(statusDefine.getCode())
                .bizData(bizSnapshot).build();

        //如果分库键为空,即数据库不进行分库,使其默认值为1
        if (null == dbShardId) {
            model.setDbShardId(DEFAULT_DB_SHARD_ID);
        }
        bizSnapshotMapper.insert(model);
    }

    /**
     * 根据业务id和状态查询最新业务快照
     *
     * @param stateMachineName 状态机名称
     * @param bizId            业务id
     * @param state            状态代码
     * @return 业务快照
     */
    @Override
    public String findLastSnapshotByBizIdAndState(String stateMachineName, String bizId, String state) {
        LambdaQueryWrapper<BizSnapshot> queryWrapper = Wrappers.<BizSnapshot>lambdaQuery()
                .eq(ObjectUtil.isNotEmpty(stateMachineName), BizSnapshot::getStateMachineName, stateMachineName)
                .eq(ObjectUtil.isNotEmpty(bizId), BizSnapshot::getBizId, bizId)
                .eq(ObjectUtil.isNotEmpty(state), BizSnapshot::getState, state)
                .gt(BizSnapshot::getDbShardId, 0)
                .orderByDesc(BizSnapshot::getCreateTime)
                .last("limit 1");
        BizSnapshot bizSnapshot = bizSnapshotMapper.selectOne(queryWrapper);
        return ObjectUtil.isNotNull(bizSnapshot)?bizSnapshot.getBizData():null;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小DuDu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值