专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Spring StateMachine状态机

什么是状态机?

定义

状态机是有限状态机的简称,是现实事物运行规则抽象而成的一个数学模型。

解释

现实世界中,各种事物都是有状态的,例如人,健康状态、生病状态、痊愈中状态。再比如一个电梯,有停止状态,运行状态。这些状态的转变,都是由于机体的事件触发。人由健康状态到生病状态,会有很多事件,吃错东西了,吃错药,不规则的作息等等等;由生病状态到痊愈中状态,需要看医生事件,吃药事件等。电梯的停止状态到运行状态,需要乘客按下楼层按钮事件等。

本文主要是订单的流转状态来做演示。我们都知道一个电商项目,必然存在的主体就是订单,而一个订单又会有很多状态:待支付(创建)、待发货、待收货、完成、取消等等。

针对以上的状态变换,涉及事件:支付、发货、确认收货、取消。

怎么使用spring StateMachine?

基础配置

1、 首先pom文件引入依赖

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

2、 编写订单状态类

    package com.yezi.statemachinedemo.business.enums;

    /**
     * @Description: 订单状态
     * @Author: yezi
     * @Date: 2020/6/19 14:01
     */
    public enum TradeStatus {
        //待支付
        TO_PAY,
        //待发货
        TO_DELIVER,
        //待收货
        TO_RECIEVE,
        //完成
        COMPLETE,
        //取消
        VOID;
    }

3、 状态流转会涉及的到事件

    package com.yezi.statemachinedemo.business.enums;

    /**
     * @Description: 订单事件
     * @Author: yezi
     * @Date: 2020/6/19 14:02
     */
    public enum TradeEvent {
        PAY, //支付
        SHIP,//发货
        CONFIRM,//确认收货
        VOID//取消
    }

4、 编写订单实体

    package com.yezi.statemachinedemo.business.entity;

    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import lombok.Data;

    import javax.persistence.*;
    import java.time.LocalDateTime;

    /**
     * @Description:
     * @Author: yezi
     * @Date: 2020/6/19 13:56
     */
    @Data
    @Entity
    @Table(name = "trade")
    public class Trade {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        /**
         * 订单状态
         */
        @Enumerated(value = EnumType.STRING)
        private TradeStatus status;

        /**
         * 订单号
         */
        private String tradeNo;

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

    }

核心配置

1、 订单状态机构建器

    package com.yezi.statemachinedemo.fsm;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.statemachine.StateMachine;

    /**
     * @Description: 订单状态机构建器
     * @Author: yezi
     * @Date: 2020/6/22 15:24
     */
    public interface TradeFSMBuilder {  

        /**
         * @return
         */
        TradeStatus supportState();

        /**
         * @param trade
         * @param beanFactory
         * @return
         * @throws Exception
         */
        StateMachine<TradeStatus, TradeEvent> build(Trade trade, BeanFactory beanFactory) throws Exception;
    }

2、 提供一个状态机工厂用以创建不同的状态机,这里spring会将状态机构建器的所有实现类自动注入tradeFSMBuilders,同时实现InitializingBean接口,在工厂类实例化同时将状态机构建起存入builderMap中。

    package com.yezi.statemachinedemo.fsm;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.statemachine.StateMachine;
    import org.springframework.stereotype.Component;

    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * @Description: 状态机工厂
     * @Author: yezi
     * @Date: 2020/6/19 17:13
     */
    @Component
    public class BuilderFactory implements InitializingBean {

        private Map<TradeStatus, TradeFSMBuilder> builderMap = new ConcurrentHashMap<>();

        @Autowired
        private List<TradeFSMBuilder> tradeFSMBuilders;

        @Autowired
        private BeanFactory beanFactory;

        public StateMachine<TradeStatus, TradeEvent> create(Trade trade) {
            TradeStatus tradeStatus = trade.getStatus();
            TradeFSMBuilder tradeFSMBuilder = builderMap.get(tradeStatus);
            if (tradeFSMBuilder == null) {
                throw new RuntimeException("构建器创建失败");
            }
            //创建订单状态机
            StateMachine<TradeStatus, TradeEvent> sm;
            try {
                sm = tradeFSMBuilder.build(trade, beanFactory);
                sm.start();
            } catch (Exception e) {
                throw new RuntimeException("状态机创建失败");
            }
            //将订单放入状态机
            sm.getExtendedState().getVariables().put(Trade.class, trade);
            return sm;
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            builderMap = tradeFSMBuilders.stream().collect(Collectors.toMap(TradeFSMBuilder::supportState, Function.identity()));
        }
    }

3、 编写状态机服务类

    package com.yezi.statemachinedemo.fsm;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import com.yezi.statemachinedemo.service.TradeService;
    import com.yezi.statemachinedemo.fsm.params.StateRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.statemachine.StateMachine;
    import org.springframework.stereotype.Service;

    import java.util.Objects;

    /**
     * @Description: 订单状态机服务
     * @Author: yezi
     * @Date: 2020/6/22 15:24
     */
    @Slf4j
    @Service
    public class TradeFSMService {

        @Autowired
        private TradeService tradeService;

        @Autowired
        private BuilderFactory builderFactory;

        /**
         * 订单状态变更
         *
         * @param request
         * @return
         */
        public boolean changeState(StateRequest request) {
            Trade trade = tradeService.findById(request.getTid());
            log.info("trade={}", trade);
            if (Objects.isNull(trade)) {
                log.error("创建订单状态机失败,无法从状态 {} 转向 => {}", trade.getStatus(), request.getEvent());
                throw new RuntimeException("订单不存在");
            }
            //1.根据订单创建状态机
            StateMachine<TradeStatus, TradeEvent> stateMachine = builderFactory.create(trade);
            //2.将参数传入状态机
            stateMachine.getExtendedState().getVariables().put(StateRequest.class, request);
            //3.发送当前请求的状态
            boolean isSend = stateMachine.sendEvent(request.getEvent());
            if (!isSend) {
                log.error("创建订单状态机失败,无法从状态 {} 转向 => {}", trade.getStatus(), request.getEvent());
                throw new RuntimeException("创建订单状态机失败");
            }
            //4. 判断处理过程中是否出现了异常
            Exception exception = stateMachine.getExtendedState().get(Exception.class, Exception.class);
            if (exception != null) {
                if (exception.getClass().isAssignableFrom(RuntimeException.class)) {
                    throw (RuntimeException) exception;
                } else {
                    throw new RuntimeException("状态机处理出现异常");
                }
            }
            return true;
        }
    }

4、 状态流转动作类

    package com.yezi.statemachinedemo.fsm;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import com.yezi.statemachinedemo.service.TradeService;
    import com.yezi.statemachinedemo.fsm.params.StateRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.statemachine.StateContext;
    import org.springframework.statemachine.action.Action;

    import java.lang.reflect.UndeclaredThrowableException;

    /**
     * @Description:
     * @Author: yezi
     * @Date: 2020/6/22 15:13
     */
    @Slf4j
    public abstract class TradeAction implements Action<TradeStatus, TradeEvent> {

        @Autowired
        private TradeService tradeService;

        @Override
        public void execute(StateContext<TradeStatus, TradeEvent> stateContext) {
            TradeStateContext tsc = new TradeStateContext(stateContext);
            try {
                evaluateInternal(tsc.getTrade(), tsc.getRequest(), tsc);
            } catch (Exception e) {
                //捕获此处异常,将异常信息放入订单状态机上下文
                tsc.put(Exception.class, e);
                if (e instanceof UndeclaredThrowableException) {
                    //如果发生包装异常,需要获取包装异常中的具体异常信息
                    Throwable undeclaredThrowable = ((UndeclaredThrowableException) e).getUndeclaredThrowable();
                    undeclaredThrowable.printStackTrace();
                    log.error(String.format("订单处理, 从状态[ %s ], 经过事件[ %s ], 到状态[ %s ], 出现异常[ %s ]", stateContext.getSource().getId(), stateContext.getEvent(), stateContext.getTarget().getId(), undeclaredThrowable));
                } else {
                    e.printStackTrace();
                    log.error(String.format("订单处理, 从状态[ %s ], 经过事件[ %s ], 到状态[ %s ], 出现异常[ %s ]", stateContext.getSource().getId(), stateContext.getEvent(), stateContext.getTarget().getId(), e));
                }

            }
        }

        /**
         * 更新订单
         *
         * @param trade
         */
        protected void update(Trade trade) {
            tradeService.update(trade);
        }

        protected abstract void evaluateInternal(Trade trade, StateRequest request, TradeStateContext tsc);
    }

5、 状态机上下文,对当前状态机上下文的包装,主要作用于存放订单处理过程中出现的异常信息

    package com.yezi.statemachinedemo.fsm;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import com.yezi.statemachinedemo.fsm.params.StateRequest;
    import org.springframework.statemachine.StateContext;
    import org.springframework.statemachine.StateMachine;

    /**
     * @Description: 订单状态上下文:对当前状态机上下文的包装,主要作用于存放订单处理过程中出现的异常信息
     * @Author: yezi
     * @Date: 2020/6/22 15:13
     */
    public class TradeStateContext {

        private StateContext<TradeStatus, TradeEvent> stateContext;

        public TradeStateContext(StateContext<TradeStatus, TradeEvent> stateContext) {
            this.stateContext = stateContext;
        }

        /**
         * 将订单处理过程中发生的异常放入订单状态上下文
         *
         * @param key
         * @param value
         * @return
         */
        public TradeStateContext put(Object key, Object value) {
            stateContext.getExtendedState().getVariables().put(key, value);
            return this;
        }

        /**
         * 获取当前状态机所处理的订单
         *
         * @return
         */
        public Trade getTrade() {
            return this.stateContext.getExtendedState().get(Trade.class, Trade.class);
        }

        /**
         * 获取当前状态机所处理的请求
         *
         * @return
         */
        public StateRequest getRequest() {
            return this.stateContext.getExtendedState().get(StateRequest.class, StateRequest.class);
        }

        /**
         * 获取操作人信息
         *
         * @return
         */
        public String getOperator() {
            return getRequest().getOperator();
        }

        /**
         * 请求数据
         *
         * @param <T>
         * @return
         */
        public <T> T getRequestData() {
            return (T) getRequest().getData();
        }

        /**
         * 当前状态机
         *
         * @return
         */
        public StateMachine<TradeStatus, TradeEvent> getStateMachine() {
            return this.stateContext.getStateMachine();
        }

        /**
         * 当前状态机上下文
         *
         * @return
         */
        public StateContext<TradeStatus, TradeEvent> getStateContext() {
            return stateContext;
        }
    }

以上为本文用到的核心配置类,当中涉及一些设计模式,就不一一介绍了。

示例

由于订单的状态流转众多,为了演示,只选取其中一种做演示。下面以订单支付为例:

1、 编写订单支付状态机构建器

    package com.yezi.statemachinedemo.fsm.builder;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeEvent;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import com.yezi.statemachinedemo.fsm.TradeFSMBuilder;
    import com.yezi.statemachinedemo.fsm.action.CancelAction;
    import com.yezi.statemachinedemo.fsm.action.PayAction;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.statemachine.StateMachine;
    import org.springframework.statemachine.config.StateMachineBuilder;
    import org.springframework.stereotype.Component;

    import java.util.EnumSet;

    /**
     * @Description:
     * @Author: yezi
     * @Date: 2020/6/19 17:13
     */
    @Component
    public class PayTradeFSMBuilder implements TradeFSMBuilder {

        @Autowired
        private PayAction payAction;

        @Autowired
        private CancelAction cancelAction;

        @Override
        public TradeStatus supportState() {
            return TradeStatus.TO_PAY;
        }

        @Override
        public StateMachine<TradeStatus, TradeEvent> build(Trade trade, BeanFactory beanFactory) throws Exception {
            StateMachineBuilder.Builder<TradeStatus, TradeEvent> builder = StateMachineBuilder.builder();

            builder.configureStates()
                    .withStates()
                    .initial(TradeStatus.TO_PAY)
                    .states(EnumSet.allOf(TradeStatus.class));

            builder.configureTransitions()
                    //待支付 -> 发货
                    .withExternal()
                    .source(TradeStatus.TO_PAY).target(TradeStatus.TO_DELIVER)
                    .event(TradeEvent.PAY)
                    .action(payAction)
                    .and()
                    //待支付 -> 取消
                    .withExternal()
                    .source(TradeStatus.TO_PAY).target(TradeStatus.VOID)
                    .event(TradeEvent.VOID)
                    .action(cancelAction);

            return builder.build();
        }
    }

待支付状态的订单当前有2种状态流转,一个是支付之后发货,一个只取消;2个状态是平行状态只是执行的动作不同。

 *  `initial(TradeStatus.TO_PAY)`表示初始状态未`TO_PAY`。
 *  `source(TradeStatus.TO_PAY).target(TradeStatus.TO_DELIVER)`表示由状态`TO_PAY`流转为`TO_DELIVER`。
 *  `event(TradeEvent.PAY)`表示触发事件。
 *  `action(payAction)`表示执行动作,也就是实际的业务逻辑。
 *  下面还有待支付到取消,如果一个状态会有多种状态流转,spring statemachine支持使用类似链式编程的方式,由不同事件出发不同动作。

2、 编写订单支付动作

    package com.yezi.statemachinedemo.fsm.action;

    import com.yezi.statemachinedemo.business.entity.Trade;
    import com.yezi.statemachinedemo.business.enums.TradeStatus;
    import com.yezi.statemachinedemo.fsm.TradeAction;
    import com.yezi.statemachinedemo.fsm.TradeStateContext;
    import com.yezi.statemachinedemo.fsm.params.StateRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;

    /**
     * @Description: 订单支付动作
     * @Author: yezi
     * @Date: 2020/6/22 15:22
     */
    @Slf4j
    @Component
    public class PayAction extends TradeAction {

        @Override
        protected void evaluateInternal(Trade trade, StateRequest request, TradeStateContext tsc) {
            pay(trade);
        }

        /**
         * 待支付状态变更为待发货状态
         *
         * @param trade
         */
        private void pay(Trade trade) {
            trade.setStatus(TradeStatus.TO_DELIVER);
            update(trade);
            log.info("订单号{},支付成功。", trade.getTradeNo());
        }
    }

此处为了做演示,此处逻辑只做简单的状态变更。

发送支付请求:

![80\_1.png][80_1.png]

结果:

![80\_2.png][80_2.png]

文章永久链接:https://tech.souyunku.com/34114

未经允许不得转载:搜云库技术团队 » Spring StateMachine状态机

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们