Spring StateMachine 状态机的使用
阅读数:125 评论数:0
跳转到新版页面分类
python/Java
正文
一、概述
Spring StateMachine是Spring官方提供的一个框架,供应用程序开发人员在spring应用程序中使用状态机。
1、四个常用概念
State (状态) | 一个状态机至少包含两个状态。 |
Event (事件 ) | 执行某个操作的触发条件或者口令。 |
Action(动作) | 事件发生以后要执行的动作。 |
Transition (变换) | 即从一个状态变化为另一个状态。 |
二、基本使用
<!-- 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>
(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;
}
@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);
}
}
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);
}
}
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;
}
}
监听可以配置在规则定义中的.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分支。
.withExternal()
.source(TradeOrderStateMachineEnum.WAIT_FOR_PAY)
.target(TradeOrderStateMachineEnum.CLOSED)
.event(TradeOrderEvent.CLOSE).and()
表示从WAIT_FOR_PAY->CLOSED,需要CLOSE事件来触发。这是比较简单的一种,没有guard判断,直接流转到CLOSED状态。
.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。
.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的
方式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、版本兼容
使用时要注意版
websocket协议基于tcp的网络协议,它实现浏览器与器全双工通信。
spring boot2 +websocket
1、添加依赖
<pre clas