搞懂状态机,看这一篇就够了

1.创作背景

由于在项目实战中要实现招商记录的状态流转功能,于是去学习了状态机这一概念,在网上查阅各种资料期间,发现存在具体用处说明不清、适用场景适配、各种配置不明等情况,所以决定写一篇总结性的博客,记录在项目中实战状态机的各种情况和遇到的问题。

2.为什么要使用状态机

(复杂概念版)

有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。主要有 4 个要素,即现态、条件、动作、次态。

  • 现态:是指当前所处的状态。
  • 条件:又称为 “事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态:条件满足后要迁往的新状态。“次态” 是相对于 “现态” 而言的,“次态” 一旦被激活,就转变成新的 “现态” 了。

(通俗易懂版)

状态机就是一个记录目标状态流转的机器。比如最简单的状态机可以是一个map,里面存放了订单的id以及订单的状态,然后每次状态流转修改map里面的值。主打一个记录状态的作用

3.实战价值 (最重要的地方)

有人可能会问(包括我),订单的状态一般不是会记录在数据库里面吗?还要状态机干什么。
问得好!接下来是重点!

注意:状态机里面的值本身其实没有多少价值,有价值的业务数据比如订单其实都存库表,状态值一般也是伴随订单一起保存就行了。

状态机核心作用:在于让我们开发的关注点不在于具体的业务数据,而是状态流程,这个作为主线,我们必须要清楚。企业开发中,一个目标的状态流转可能是非常非常复杂的,会有分支选择,会有回到上一个状态的情况,所以状态机就是让我们后续参与开发的人员可以不用了解整个复杂逻辑,只关注起始状态变化就能参与开发。

4.状态机组件介绍(以Spring StateMachine为例)

选型:开源的状态机引擎有很多,目前在 Github 上的 Top 2 状态机实现中,一个是 Spring StateMachine,一个是 Squirrel StateMachine。关于状态机选型就不过多赘述。本文采用的案例是Spring StateMachine,它功能非常强大,有很多应对复杂需求的功能,由于本文只介绍简单的状态机模型案例,更多详细用法可以去官网上查询:
Spring官网:https://spring.io/projects/spring-statemachine
GitHub官网:https://github.com/spring-projects/spring-statemachine

在这里插入图片描述

5.上手实战

下面以一个简单的订单状态流转功能做个案例

引入依赖

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

创建状态(status)事件(event)枚举类


/**
 * 订单状态
 */
public enum OrderStatus {
    // 待支付,待发货,待收货,订单结束
    WAIT_PAYMENT, WAIT_DELIVER, WAIT_RECEIVE, FINISH;
}

/**
 * 订单状态改变事件
 */
public enum OrderStatusChangeEvent {
    // 支付,发货,确认收货
    PAYED, DELIVERY, RECEIVED;
}

状态机配置类

StateMachineConfig,它的主要作用就告诉状态机的初始状态应该啥样,然后把整个状态流程都用代码配置出来。得用@EnableStateMachine注解声明这是状态机配置

/**
 * 订单状态机配置
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
 
    /**
     * 配置状态
     * @param states
     * @throws Exception
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
        states
                .withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    }
 
    /**
     * 配置状态转换事件关系
     * @param transitions
     * @throws Exception
     */
    @Override
    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);
    }
 
    /**
     * 持久化配置
     * 实际使用中,可以配合redis等,进行持久化操作
     * @return
     */
    @Bean
    public StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister(){
        return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatus, OrderStatusChangeEvent, Order>() {
            @Override
            public void write(StateMachineContext<OrderStatus, OrderStatusChangeEvent> context, Order order) throws Exception {
                //此处并没有进行持久化操作
            }
 
            @Override
            public StateMachineContext<OrderStatus, OrderStatusChangeEvent> read(Order order) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext<>(order.getStatus(), null, null, null);
            }
        });
    }
}

监听配置

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl{
 
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER);
        System.out.println("支付 headers=" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_RECEIVE);
        System.out.println("发货 headers=" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message<OrderStatusChangeEvent> message){
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.FINISH);
        System.out.println("收货 headers=" + message.getHeaders().toString());
        return true;
    }
}

Service中调用

@Service("orderService")
public class OrderServiceImpl implements OrderService {
 
    @Autowired
    private OrderMapper orderMapper;
 
    @Autowired
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
 
    @Autowired
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;
 
    private int id = 1;
    private Map<Integer, Order> orders = new HashMap<>();
 
    @Override
    public Order creat() {
        Order order = new Order();
        order.setStatus(OrderStatus.WAIT_PAYMENT);
        order.setId(id++);
        orders.put(order.getId(), order);
        return order;
    }
 
    @Override
    public Order pay(int id) {
        Order order = orders.get(id);
        System.out.println("threadName=" + Thread.currentThread().getName() + " 尝试支付 id=" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build();
        if (!sendEvent(message, order)) {
            System.out.println("threadName=" + Thread.currentThread().getName() + " 支付失败, 状态异常 id=" + id);
        }
        return orders.get(id);
    }
 
    @Override
    public Order deliver(int id) {
        Order order = orders.get(id);
        System.out.println("threadName=" + Thread.currentThread().getName() + " 尝试发货 id=" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("threadName=" + Thread.currentThread().getName() + " 发货失败,状态异常 id=" + id);
        }
        return orders.get(id);
    }
 
    @Override
    public Order receive(int id) {
        Order order = orders.get(id);
        System.out.println("threadName=" + Thread.currentThread().getName() + " 尝试收货 id=" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("threadName=" + Thread.currentThread().getName() + " 收货失败,状态异常 id=" + id);
        }
        return orders.get(id);
    }
 
    @Override
    public Map<Integer, Order> getOrders() {
        return orders;
    }
 
 
    /**
     * 发送订单状态转换事件
     *
     * @param message
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) {
        boolean result = false;
        try {
            orderStateMachine.start();
            //尝试恢复状态机状态
            persister.restore(orderStateMachine, order);
            //添加延迟用于线程安全测试
            Thread.sleep(1000);
            result = orderStateMachine.sendEvent(message);
            //持久化状态机状态
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }
}

存在问题

以上是一个最简单的状态机的使用案例,相信很多状态机介绍的问题都有这个案例,可是单凭这些代码真的能搞懂整个状态机的流转吗?完全不是!第一次看完的我就有以下几个问题:

  1. 整个项目只有一种状态机流程,我要是想在一个项目里面又有订单流程,又有公文审批流程怎么办
  2. 整个项目只有一个状态机流程,就已经显得特别复杂了,根本没有觉得方便,这是另外一个问题。哪怕是只有一种流程,比如订单流程,其实也是有很多订单的流程在同时跑,而不是像这个例子,全部订单共用一个流程,一个订单到DELIVERY状态了,其他订单就不能是UNPAY状态了。
  3. 参数问题,我们做项目,不是为了看日志打出“—订单创建,待支付—”给我们玩的,而是要处理具体业务的,拿订单流程来说吧,订单号怎么传,状态改变时间怎么回数据库,等等问题其实都需要解决。
  4. 持久化问题,状态机如果有多个,需要的时候从哪去,暂时不需要了放哪去,这都是问题,所以存储状态机也是需要解决的。

6.企业实战问题以及解决方案

问题一:多个状态机使用问题

首先,靠上一章例子里面的手打定制一个StateMachineConfig的做法,就只能是有一个状态机流程制霸整个项目,这种霸道的做法肯定是不行啦,要想多个状态机流程并行,那么就要请builder出场了,看代码:

private final static String MACHINEID = "orderMachine";
public StateMachine<OrderStates, OrderEvents> build(BeanFactory beanFactory) throws Exception {
		 StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder();
		 
		 System.out.println("构建订单状态机");
		 
		 builder.configureConfiguration()
		 		.withConfiguration()
                .machineId(MACHINEID)
		 		.beanFactory(beanFactory);
		 
		 builder.configureStates()
		 			.withStates()
		 			.initial(OrderStates.UNPAID)
		 			.states(EnumSet.allOf(OrderStates.class));
		 			
		 builder.configureTransitions()
					 .withExternal()
						.source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_RECEIVE)
						.event(OrderEvents.PAY).action(action())
						.and()
					.withExternal()
						.source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE)
						.event(OrderEvents.RECEIVE);
		 			
		 return builder.build();
	 }

适用建造者模式,通过一个建造者builder来构建状态机配置信息

同时,如果我们要在一个业务里面调用很多个状态机,比如有订单状态机、招商记录状态机要一起用,那么我们可以给每一种状态机声明一个MACHINEID

builder.configureConfiguration()
		 		.withConfiguration()
		 		.machineId(MACHINEID)
		 		.beanFactory(beanFactory);

类上要声明这个注解

@WithStateMachine(id="formMachine")
public class FormEventConfig {

然后service类里面直接引入这两个类就行了

	@Autowired
	private OrderStateMachineBuilder orderStateMachineBuilder;
	
	@Autowired
	private FormStateMachineBuilder formStateMachineBuilder;

问题二:状态机状态记录日志问题

如何获取当前状态机的信息呢?在企业开发中,我们肯定要记录日志功能,以便于在出现问题的时候快速定位这个问题呢?除了监听配置,还可以用到Message类,也是我们上面这段代码的作用:

	/**
     * 发送订单状态转换事件
     *
     * @param message
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) {
        boolean result = false;
        try {
            orderStateMachine.start();
            //尝试恢复状态机状态
            persister.restore(orderStateMachine, order);
            //添加延迟用于线程安全测试
            Thread.sleep(1000);
            result = orderStateMachine.sendEvent(message);
            //持久化状态机状态
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

它其实不是spirng statemachine专属的,它是spring里面通用的一种消息工具,看它的源代码:

package org.springframework.messaging;
 
public interface Message<T> {
 
	/**
	 * Return the message payload.
	 */
	T getPayload();
 
	/**
	 * Return message headers for the message (never {@code null} but may be empty).
	 */
	MessageHeaders getHeaders();
 
}

类似于前后端发送的request、response请求,message会把信息存放到headers请求头中,每次读取时,也可以从请求头中读取信息。

在实战中,我们可以把整个订单的信息存储到message中,例如:
存储信息:

Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherobj", "otherobjvalue").build();

读取信息:

System.out.println("传递的参数:" + message.getHeaders().get("order"));
System.out.println("传递的参数:" + message.getHeaders().get("otherObj"));

问题三:状态机的持久化 (重要)

状态机为什么要持久化?

目前位置我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在spring statemachine中,给出来的办法就是保存起来,到需要的时候取出来用

1.持久化到本地内存

非常简单,你甚至可以直接存到一个map里面

@Bean(name = "recordStateMachinePersister")
    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 ConcurrentHashMap map = new ConcurrentHashMap();
        });
    }
 
}

然后取出来即可

recordStateMachine.start();
recordStateMachinePersister.restore(recordStateMachine, String.valueOf(recruitId));
Message message = MessageBuilder.withPayload(changeEvent).setHeader("recruit_id", recruitId).build();
result = recordStateMachine.sendEvent(message);
recordStateMachinePersister.persist(recordStateMachine, String.valueOf(recruitId));
2.持久化到redis

真正的业务中,一般都是多台机分布式运行,所以如果状态机只能保存在本地内容,就不能用在分布式应用上了。spring提供了一个方便的办法,使用redis解决这个问题。让我们看看怎么弄。

pom文件引入spring-statemachine-redis

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

在springboot配置文件里面加上redis参数,以application.properties为例

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

持久化配置:

@Configuration
public class PersistConfig {
	
	@Autowired
	private RedisConnectionFactory redisConnectionFactory;
	
	
	/**
	 * 注入RedisStateMachinePersister对象
	 * 
	 * @return
	 */
	@Bean(name = "orderRedisPersister")
	public RedisStateMachinePersister<OrderStates, OrderEvents> redisPersister() {
		return new RedisStateMachinePersister<>(redisPersist());
	}
 
	/**
	 * 通过redisConnectionFactory创建StateMachinePersist
	 * 
	 * @return
	 */
	public StateMachinePersist<OrderStates, OrderEvents,String> redisPersist() {
		RedisStateMachineContextRepository<OrderStates, OrderEvents> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
		return new RepositoryStateMachinePersist<>(repository);
	}
	
}

这个套路和上面保存到本地内存是一样一样的,先生成一个StateMachinePersist,这里是通过RedisConnectionFactory生成RepositoryStateMachinePersist,然后再包装输出StateMachinePersister,这里是RedisStateMachinePersister。

3.伪持久化和中间段的状态机

重点

我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,我们只需要从订单表里面把状态取出来,知道当前是什么状态,然后伴随着业务继续流浪到下一个状态节点就好了。

在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程…,所以这种可以任意调节状态的才是我们需要的状态机

比如我们先实现一个StateMachinePersist

import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Component;
 
@Component
public class OrderStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, Order> {
 
	@Override
	public void write(StateMachineContext<OrderStates, OrderEvents> context, Order contextObj) throws Exception {
		//这里不做任何持久化工作
	}
 
	@Override
	public StateMachineContext<OrderStates, OrderEvents> read(Order contextObj) throws Exception {
		StateMachineContext<OrderStates, OrderEvents> result = new DefaultStateMachineContext<OrderStates, OrderEvents>(OrderStates.valueOf(contextObj.getState()), 
				null, null, null, null, "orderMachine");
		return result;
	}
}

然后在PersistConfig里面转换成StateMachinePersister

@Configuration
public class PersistConfig {
@Autowired
    private OrderStateMachinePersist orderStateMachinePersist;
@Bean(name="orderPersister")
    public StateMachinePersister<OrderStates, OrderEvents, Order> orderPersister() {
		return new DefaultStateMachinePersister<OrderStates, OrderEvents, Order>(orderStateMachinePersist);
	}
}

现在问题来了,不持久化的持久化类是为啥呢,主要就是为了取一个任何状态节点的状态机
案例:

@RestController
@RequestMapping("/statemachine")
public class StateMachineController {
 
    @Resource(name="orderPersister")
	private StateMachinePersister<OrderStates, OrderEvents, Order> persister;
    
    @RequestMapping("/testOrderRestore")
	public void testOrderRestore(String id) throws Exception {
		StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
		//订单
		Order order = new Order();
		order.setId(id);
		order.setState(OrderStates.WAITING_FOR_RECEIVE.toString());
		//恢复
		persister.restore(stateMachine, order);
		//查看恢复后状态机的状态
		System.out.println("恢复后的状态:" + stateMachine.getState().getId());
	}
}

我们这里用builder建了一个新的状态机,用restore过了一手,就已经是一个到达order指定状态的老司机状态机了,在这里,持久化不是本意,让状态机能够随时切换到任意状态节点才是目的。在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程…,所以这种可以任意调节状态的才是我们需要的状态机。(反复强调)

7.复杂情况的状态流转扩展

实际情况中,我们遇到的状态流转会更复杂,可能会出现有分支状态,比如订单支付状态要么支付成功要么支付失败,如何设置我们的条件呢
这里举一个例子:

public enum ComplexFormStates {
	BLANK_FORM, // 空白表单
	FULL_FORM, // 填写完表单
	CHECK_CHOICE,//表单校验判断
	DEAL_CHOICE,//表单处理校验
	DEAL_FORM,//待处理表单
	CONFIRM_FORM, // 校验完表单
	SUCCESS_FORM,// 成功表单
	FAILED_FORM//失败表单
}

注意一点,choice判断分支本身也是一种状态,要声明出来,这里是CHECK_CHOICE和DEAL_CHOICE

public enum ComplexFormEvents {
	WRITE, // 填写
	CHECK,//校验
	DEAL,//处理
	SUBMIT // 提交
}

同样的分支事件也要声明,这里是CHECK和DEAL。

/**
 * 复杂订单状态机构建器
 */
@Component
public class ComplexFormStateMachineBuilder {
 
	private final static String MACHINEID = "complexFormMachine";
	
	 /**
	  * 构建状态机
	  * 
	 * @param beanFactory
	 * @return
	 * @throws Exception
	 */
	public StateMachine<ComplexFormStates, ComplexFormEvents> build(BeanFactory beanFactory) throws Exception {
		 StateMachineBuilder.Builder<ComplexFormStates, ComplexFormEvents> builder = StateMachineBuilder.builder();
		 
		 System.out.println("构建复杂表单状态机");
		 
		 builder.configureConfiguration()
		 		.withConfiguration()
		 		.machineId(MACHINEID)
		 		.beanFactory(beanFactory);
		 
		 builder.configureStates()
		 			.withStates()
		 			.initial(ComplexFormStates.BLANK_FORM)
		 			.choice(ComplexFormStates.CHECK_CHOICE)
		 			.choice(ComplexFormStates.DEAL_CHOICE)
		 			.states(EnumSet.allOf(ComplexFormStates.class));
		 			
		 builder.configureTransitions()
					.withExternal()
						.source(ComplexFormStates.BLANK_FORM).target(ComplexFormStates.FULL_FORM)
						.event(ComplexFormEvents.WRITE)
						.and()
					.withExternal()
						.source(ComplexFormStates.FULL_FORM).target(ComplexFormStates.CHECK_CHOICE)
						.event(ComplexFormEvents.CHECK)
						.and()
					.withChoice()
						.source(ComplexFormStates.CHECK_CHOICE)
						.first(ComplexFormStates.CONFIRM_FORM, new ComplexFormCheckChoiceGuard())
						.last(ComplexFormStates.DEAL_FORM)
						.and()
					.withExternal()
						.source(ComplexFormStates.CONFIRM_FORM).target(ComplexFormStates.SUCCESS_FORM)
						.event(ComplexFormEvents.SUBMIT)
						.and()
					.withExternal()
						.source(ComplexFormStates.DEAL_FORM).target(ComplexFormStates.DEAL_CHOICE)
						.event(ComplexFormEvents.DEAL)
						.and()
					.withChoice()
						.source(ComplexFormStates.DEAL_CHOICE)
						.first(ComplexFormStates.FULL_FORM, new ComplexFormDealChoiceGuard())
						.last(ComplexFormStates.FAILED_FORM);
		 return builder.build();
	 }
}

有几点要注意的是:

  • 在configureStates时,要把每个分支都要写上去(不然会失效)
  • 在我们熟悉的withExternal之后,我们迎来了为choice专门准备的withChoice()和跟随它的first(),last()。这两个代表的就是分支判断时TRUE和FALSE的状态流程去处,这里面涉及到的一个问题就是,TRUE和FALSE的判断条件是什么呢,根据啥来判断呢?
  • Guard就承担了这个判断的功能。它在spring statemachine本来是用来保护这个状态跳转过程的,所以用guard,但在choice里面,它就是作为判断代码而存在的。代码如下:
public class ComplexFormCheckChoiceGuard implements Guard<ComplexFormStates, ComplexFormEvents> {
 
	@Override
	public boolean evaluate(StateContext<ComplexFormStates, ComplexFormEvents> context) {
		boolean returnValue = false;
		Form form = context.getMessage().getHeaders().get("form", Form.class);
		if (form.formName == null) {
			returnValue = false;
		} else {
			returnValue = true;
		}
		return returnValue;
	}
 
}

也可以通过申明bean方法注入

因为我们目前执行事件的代码都是写到了service里面,其实我们可以使用action方法,我们完全可以在每个状态变化的时候独立写一个action,这样的话就能做到业务的互不打扰。比如下面这段:

.withChoice()
.source(ComplexFormStates.CHECK_CHOICE)
.first(ComplexFormStates.CONFIRM_FORM, new ComplexFormCheckChoiceGuard(),new ComplexFormChoiceAction())
.last(ComplexFormStates.DEAL_FORM,new ComplexFormChoiceAction())
.and()

action代码如下:

public class ComplexFormChoiceAction implements Action<ComplexFormStates, ComplexFormEvents> {
 
	@Override
	public void execute(StateContext<ComplexFormStates, ComplexFormEvents> context) {
		System.out.println("into ComplexFormChoiceAction");
		Form form = context.getMessage().getHeaders().get("form", Form.class);
		System.out.println(form);
		System.out.println(context.getStateMachine().getState());
	}
 
}

总结划重点

我们配置状态机配置主要有几个属性:

  1. source:起始状态
  2. target:目标状态
  3. withchoice:分支选择状态
  4. guard:判断进入哪个分支的条件
  5. first :条件为true时候
  6. last:条件为false时候
  7. action:满足条件后完成的业务代码

状态机里面的值本身其实没有多少价值,有价值的业务数据比如订单其实都存库表,状态值一般也是伴随订单一起保存就行了

在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程…,所以这种可以任意调节状态的才是我们需要的状态机

文章参考:https://gitee.com/wphmoon/statemachine#https://gitee.com/link

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值