Spring StateMachine 状态机的使用

阅读数:125 评论数:0

跳转到新版页面

分类

python/Java

正文

一、概述

Spring StateMachine是Spring官方提供的一个框架,供应用程序开发人员在spring应用程序中使用状态机。

1、四个常用概念

State (状态) 一个状态机至少包含两个状态。
Event (事件 ) 执行某个操作的触发条件或者口令。
Action(动作) 事件发生以后要执行的动作。
Transition (变换) 即从一个状态变化为另一个状态。

二、基本使用

1、依赖

    <!-- redis持久化状态机 -->
    <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-redis</artifactId>
        <version>1.2.9.RELEASE</version>
    </dependency>
    <!--状态机-->
    <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-starter</artifactId>
        <version>2.0.1.RELEASE</version>
    </dependency>

2、定义状态机状态和事件 

(1)状态枚举

    public enum OrderStatus {
        // 待支付,待发货,待收货,已完成
        WAIT_PAYMENT(1, "待支付"),
        WAIT_DELIVER(2, "待发货"),
        WAIT_RECEIVE(3, "待收货"),
        FINISH(4, "已完成");
        private Integer key;
        private String desc;
        OrderStatus(Integer key, String desc) {
            this.key = key;
            this.desc = desc;
        }
        public Integer getKey() {
            return key;
        }
        public String getDesc() {
            return desc;
        }
        public static OrderStatus getByKey(Integer key) {
            for (OrderStatus e : values()) {
                if (e.getKey().equals(key)) {
                    return e;
                }
            }
            throw new RuntimeException("enum not exists.");
        }
    }

(2)事件枚举

    public enum OrderStatusChangeEvent {
        // 支付,发货,确认收货
        PAYED, DELIVERY, RECEIVED;
    }

3、定义状态机规则和配置状态机

   @Configuration
    @EnableStateMachine(name = "orderStateMachine")
    public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
        /**
         * 配置状态
         *
         * @param states
         * @throws Exception
         */
        public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
            states
                    .withStates()
                    .initial(OrderStatus.WAIT_PAYMENT)
                    .states(EnumSet.allOf(OrderStatus.class));
        }
        /**
         * 配置状态转换事件关系
         *
         * @param transitions
         * @throws Exception
         */
        public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
            transitions
                    //支付事件:待支付-》待发货
                    .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                    .and()
                    //发货事件:待发货-》待收货
                    .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                    .and()
                    //收货事件:待收货-》已完成
                    .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
        }
    }

4、配置持久化

   import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.statemachine.StateMachineContext;
    import org.springframework.statemachine.StateMachinePersist;
    import org.springframework.statemachine.persist.DefaultStateMachinePersister;
    import org.springframework.statemachine.persist.RepositoryStateMachinePersist;
    import org.springframework.statemachine.persist.StateMachinePersister;
    import org.springframework.statemachine.redis.RedisStateMachineContextRepository;
    import org.springframework.statemachine.redis.RedisStateMachinePersister;
    import javax.annotation.Resource;
    import java.util.HashMap;
    import java.util.Map;
    @Configuration
    @Slf4j
    public class Persist<E, S> {
        /**
         * 持久化到内存map中
         *
         * @return
         */
        @Bean(name = "stateMachineMemPersister")
        public static StateMachinePersister getPersister() {
            return new DefaultStateMachinePersister(new StateMachinePersist() {
                @Override
                public void write(StateMachineContext context, Object contextObj) throws Exception {
                    log.info("持久化状态机,context:{},contextObj:{}", JSON.toJSONString(context), JSON.toJSONString(contextObj));
                    map.put(contextObj, context);
                }
                @Override
                public StateMachineContext read(Object contextObj) throws Exception {
                    log.info("获取状态机,contextObj:{}", JSON.toJSONString(contextObj));
                    StateMachineContext stateMachineContext = (StateMachineContext) map.get(contextObj);
                    log.info("获取状态机结果,stateMachineContext:{}", JSON.toJSONString(stateMachineContext));
                    return stateMachineContext;
                }
                private Map map = new HashMap();
            });
        }
    
        @Resource
        private RedisConnectionFactory redisConnectionFactory;
        /**
         * 持久化到redis中,在分布式系统中使用
         *
         * @return
         */
        @Bean(name = "stateMachineRedisPersister")
        public RedisStateMachinePersister<E, S> getRedisPersister() {
            RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
            RepositoryStateMachinePersist p = new RepositoryStateMachinePersist<>(repository);
            return new RedisStateMachinePersister<>(p);
        }
    }

5、业务使用

   import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.zengqingfa.springboot.state.demo.entity.Order;
    import com.zengqingfa.springboot.state.demo.enums.OrderStatus;
    import com.zengqingfa.springboot.state.demo.enums.OrderStatusChangeEvent;
    import com.zengqingfa.springboot.state.demo.mapper.OrderMapper;
    import com.zengqingfa.springboot.state.demo.service.OrderService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.support.MessageBuilder;
    import org.springframework.statemachine.StateMachine;
    import org.springframework.statemachine.persist.StateMachinePersister;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    @Service("orderService")
    @Slf4j
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
        @Resource
        private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
        @Resource
        private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;
        @Resource
        private OrderMapper orderMapper;
        /**
         * 创建订单
         *
         * @param order
         * @return
         */
        public Order create(Order order) {
            order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
            orderMapper.insert(order);
            return order;
        }
        /**
         * 对订单进行支付
         *
         * @param id
         * @return
         */
        public Order pay(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("线程名称:{},尝试支付,订单号:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.PAYED, order)) {
                log.error("线程名称:{},支付失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("支付失败, 订单状态异常");
            }
            return order;
        }
        /**
         * 对订单进行发货
         *
         * @param id
         * @return
         */
        public Order deliver(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("线程名称:{},尝试发货,订单号:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.DELIVERY, order)) {
                log.error("线程名称:{},发货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("发货失败, 订单状态异常");
            }
            return order;
        }
        /**
         * 对订单进行确认收货
         *
         * @param id
         * @return
         */
        public Order receive(Long id) {
            Order order = orderMapper.selectById(id);
            log.info("线程名称:{},尝试收货,订单号:{}" ,Thread.currentThread().getName() , id);
            if (!sendEvent(OrderStatusChangeEvent.RECEIVED, order)) {
                log.error("线程名称:{},收货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);
                throw new RuntimeException("收货失败, 订单状态异常");
            }
            return order;
        }
        /**
         * 发送订单状态转换事件
         * synchronized修饰保证这个方法是线程安全的
         *
         * @param changeEvent
         * @param order
         * @return
         */
        private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
            boolean result = false;
            try {
                //启动状态机
                orderStateMachine.start();
                //尝试恢复状态机状态
                stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
                Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
                result = orderStateMachine.sendEvent(message);
                //持久化状态机状态
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            } catch (Exception e) {
                log.error("订单操作失败:{}", e);
            } finally {
                orderStateMachine.stop();
            }
            return result;
        }
    }

6、监听状态的变化

监听可以配置在规则定义中的.action()外,还可以使用@WithStateMachine和@OnTransition注解。

   package com.zengqingfa.springboot.state.demo.listener;
    import com.zengqingfa.springboot.state.demo.entity.Order;
    import com.zengqingfa.springboot.state.demo.enums.OrderStatus;
    import com.zengqingfa.springboot.state.demo.enums.OrderStatusChangeEvent;
    import com.zengqingfa.springboot.state.demo.mapper.OrderMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.messaging.Message;
    import org.springframework.statemachine.annotation.OnTransition;
    import org.springframework.statemachine.annotation.WithStateMachine;
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    
    @Component("orderStateListener")
    @WithStateMachine(name = "orderStateMachine")
    @Slf4j
    public class OrderStateListenerImpl {
        @Resource
        private OrderMapper orderMapper;
        
        @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
        public void payTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("支付,状态机反馈信息:{}",  message.getHeaders().toString());
            //更新订单
            order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
            orderMapper.updateById(order);
            //TODO 其他业务
        }
        @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
        public void deliverTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("发货,状态机反馈信息:{}",  message.getHeaders().toString());
            //更新订单
            order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
            orderMapper.updateById(order);
            //TODO 其他业务
        }
        @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
        public void receiveTransition(Message<OrderStatusChangeEvent> message) {
            Order order = (Order) message.getHeaders().get("order");
            log.info("确认收货,状态机反馈信息:{}",  message.getHeaders().toString());
            //更新订单
            order.setStatus(OrderStatus.FINISH.getKey());
            orderMapper.updateById(order);
            //TODO 其他业务
        }
    }

三、状态机规则详述

   builder.configureTransitions()  -- 配置节点
            .withExternal()   //表示source target两种状态不同
            .source(CREATE)  //当前节点状态
            .target(WYD_INITIAL_JUMP)  //目标节点状态,这里是设置了个中间状态
            .event(BizOrderStatusChangeEventEnum.EVT_CREATE)  //导致当前变化的动作/事件
            .action(orderCreateAction, errorHandlerAction) //执行当前状态变更导致的业务逻辑处理,以及出异常时的处理

(1)withExternal 是当source和target不同时的写法

(2)withInternal 当source和target相同时的串联写法,比如付款失败时,付款前及付款后都是待付款状态

(3)withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转

(4)多个节点之间使用and()串联

            .and()  // 使用and串联
            .withChoice() // 使用choice来做选择
            .source(WYD_INITIAL_JUMP) // 当前状态
            .first(WAIT_REAL_NAME_AUTH, needNameAuthGurad(), needNameAuthAction)  // 第一个分支
            .last(WAIT_BORROW, waitBorrowAction) // 第二个分支

(1)needNameAuthGurad() 用于判断是否走当前分支,返回true时选择当前分支,返回false走下面分支

(2)last中其实是省略了一个guard的实现,默认为true,意味着如果不走first分支,就会走last分支。

1、简单的事件型 

.withExternal()
.source(TradeOrderStateMachineEnum.WAIT_FOR_PAY)
.target(TradeOrderStateMachineEnum.CLOSED)
.event(TradeOrderEvent.CLOSE).and()

表示从WAIT_FOR_PAY->CLOSED,需要CLOSE事件来触发。这是比较简单的一种,没有guard判断,直接流转到CLOSED状态。

2、增加guard判断

.withExternal()
.source(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT)
.target(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER)
.event(TradeOrderEvent.AUDIT).guard(tradeOrderGuardFactory.new TradeOrderGuard()).and()

.guard(),这是判断,相当于java里面if的判断条件,这个自定义判断的类必须实现Guard接口,重写里面evaluate方法,这个方法就是返回boolean。

3、更复杂的

.withChoice()
.source(TradeOrderStateMachineEnum.AUDIT_CHOICE)
.first(TradeOrderStateMachineEnum.COMPLETED, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard(),new TradeOrderChoiceAction())
.then(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard2(),new TradeOrderChoiceAction())
.then(TradeOrderStateMachineEnum.WAIT_FOR_SIGN, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard3(),new TradeOrderChoiceAction())
.then(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard4(),new TradeOrderChoiceAction())
.last(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT).and()

前面两种是比较简单的,一个事件只会流转到一个状态,上面这个就是比较复杂点,也是业务上经常会用,一个event会有几种状态,first-then-last,就相当于if-else if,只要满足一个guard判断就不会往下流转。




相关推荐

(1)Spring MVC是一个基于DispatcherServlet的MVC框架,DispatcherServlet是继承自HttpServlet的。Spring的IOC和AOP主要就用了java的

如果一个类交张spring管理,比如 &lt;bean id="foo1" class="demo.scope.foo" /&gt; 不指明scope就是单例。</p

@PathParam的声明允许你在URI路径中去映射你的方法将使用的参数。 @Path("/library") pu

方式1:通过@PostConstruct和@PreDestroy方法。 从Java EE5开始,Servlet增加了两个影响Servlet生命周期的注解。 方式2:通

一、概述 1、spring容器 spring中有三种bean容器:BeanFactory、ApplicationContext、WebApplicationContext。 (1)BeanFactor

有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,然后这样如果执行大量测试的话就很麻烦,并且不直观,如果想对执行的时间做进

一、request uri部分 @PathVariable 获取路径参数,形如url/{id} 二、request header部分 @RequestHeade

一、概述 springboot中有两种方式使用kafka时,直接使用kafka-client连接kafka服务;另一种是使用spring-kafka框架来连接kafka。 1、版本兼容 使用时要注意版

当然可以自己写redis的工具类,或者使用第三方开源jar包或代码,这里使用spring boot的集成类。 一、pom依赖 <dependency> <gro

websocket协议基于tcp的网络协议,它实现浏览器与器全双工通信。 spring boot2 +websocket 1、添加依赖 <pre clas