一.简介
状态机模式通常用于定义一个对象在有限状态间流转的行为,比如常见的订单中,通常有待支付,已支付,已发货,已完成,已取消,已退款等, 且这些状态的转换都是提前定义好的。这样可以用状态机来定义各个状态间的流转事件和触发事件,比如从待支付到已支付需要触发支付操作,而在触发的事件里可以定义不同的操作,比如支付完成之后发放优惠券等。
二.依赖
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>最新版本</version>
</dependency>
三.实战
此处贴出我在项目中使用的状态机的UML图, 其中部分状态是在此项目中的特殊业务状态:
(1)定义状态枚举
状态枚举用于定义状态机会变更到的所有状态
public enum OrderState {
INIT(1, "已创建"),
WAITING_FOR_PAY(5, "待支付"),
WAITING_FOR_ASSIGNED(9,"待分配"),
ASSIGNED_PART(10,"部分分配"),
WAITING_FOR_DELIVER(13, "待发货"),
WAITING_FOR_LOCK(17, "待锁单"),
COMPLETE(21, "已完成"),
CANCEL(25, "已取消"),
WAITING_FOR_REFUND(29, "待退款"),
REFUNDED(33, "已退款"),
;
private String msg;
private int code;
OrderState(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return this.code;
}
public String getMsg() {
return this.msg;
}
public static OrderState getInstance(int code) {
for (OrderState value : values()) {
if (value.code == code) {
return value;
}
}
return null;
}
}
(2)定义事件枚举
事件枚举是状态机会触发的所有事件
public enum OrderEvent {
CREATE_ORDER(1, "创建订单"),
PAY(2, "支付"),
CANCEL_ORDER(3, "取消订单"),
ASSIGNED(4, "分配号码"),
PART_ASSIGNED(5, "部分分配"),
CANCEL_ASSIGNED(6, "取消分配"),
PART_CANCEL_ASSIGNED(7, "部分取消分配"),
DELIVERY(8, "发货"),
LOCK(9, "锁单"),
APPLY_FOR_REFUND(10, "申请退款"),
REFUND(11, "退款"),
;
private String msg;
private int code;
OrderEvent(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
(3)状态机ID
状态机ID是每个状态机的唯一ID, 此处用枚举保存, 也可以用常量
public enum OrderStateMachineIdEnum {
CARD_ORDER("ORDER_STATE_MACHINE", "订单状态机");
private String machineId;
private String name;
OrderStateMachineIdEnum(String machineId, String name) {
this.machineId = machineId;
this.name = name;
}
public String getMachineId() {
return this.machineId;
}
public String getName() {
return this.name;
}
}
StateMachine<OrderState, OrderEvent, OrderContext> orderStateMachine = StateMachineFactory.get(OrderStateMachineIdEnum.CARD_ORDER.name());
// 此方法用于获取状态机的UML图 生成的是字符串 需要使用工具转换成上面的UML图(直接找个在线工具即可)
System.out.println(orderStateMachine.generatePlantUML());
(4)context
context 是用于在状态机中传输数据的上下文
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderContext {
// 订单编号
private String orderNo;
// 其他参数 此处使用超类 Object 可以接收任意类型, 使用时根据类型转换
private Object context;
}
(5)conditon
condition是用于定义从一个状态扭转到另一个状态的必须条件, 只有返回值为true会执行后续的action
@Slf4j
@Component
public class OrderCheckCancelCondition implements Condition<OrderContext> {
@Override
public boolean isSatisfied(OrderContext orderContext) {
log.info("订单:{},取消前校验完成", orderContext.getOrderNo());
return true;
}
}
(6)action
action是用于定义从一个状态扭转到另一个状态之后触发的动作
@Slf4j
@Component
@AllArgsConstructor
public class OrderCreateAction implements Action<OrderState, OrderEvent, OrderContext> {
private final IOrderService orderService;
@Override
public void execute(OrderState from, OrderState to, OrderEvent orderEvent, OrderContext orderContext) {
log.info("创建订单,订单编号:{}", orderContext.getOrderNo());
CreateOrderDTO createOrderDTO = (CreateOrderDTO) orderContext.getContext();
orderService.doCreateOrder(to, orderContext.getOrderNo(), createOrderDTO);
}
}
(7)状态机配置
cola
状态机可以定义内部状态流转,外部状态流转,批量状态流转
externalTransition
用于定义外部状态流转, from
表示当前状态, to
表示扭转后的状态,when
表示需要满足的条件, perform
表示需要执行的动作(action)
internalTransition
用于定于内部状态流转, within
表示状态, 内部状态流转不用定义to
externalTransitions
用于定义批量状态流转, 也就是从多个状态可以抵达同一个状态,比如: 订单在已发货,已锁单等状态都可以进行退款操作, fromAmong
表示多个from
的状态
@Component
@AllArgsConstructor
public class OrderStateMachine {
private final OrderActionHelper orderActionHelper;
private final OrderConditionHelper orderConditionHelper;
@PostConstruct
public StateMachine<OrderState, OrderEvent, OrderContext> createStateMachine() {
// 创建状态机
StateMachineBuilder<OrderState, OrderEvent, OrderContext> builder = StateMachineBuilderFactory.create();
// 外部状态流转 从init到待支付 需要触发创建订单操作
builder.externalTransition()
.from(OrderState.INIT)
.to(OrderState.WAITING_FOR_PAY)
.on(OrderEvent.CREATE_ORDER)
.when(orderConditionHelper.getOrderCheckCreateCondition())
.perform(orderActionHelper.getOrderCreateAction());
// 外部状态流转 从待支付到已取消 需要触发取消订单操作
builder.externalTransition()
.from(OrderState.WAITING_FOR_PAY)
.to(OrderState.CANCEL)
.on(OrderEvent.CANCEL_ORDER)
.when(orderConditionHelper.getOrderCheckCancelCondition())
.perform(orderActionHelper.getOrderCancelAction());
// 外部状态流转 从待支付到待分配 需要触发支付操作
builder.externalTransition()
.from(OrderState.WAITING_FOR_PAY)
.to(OrderState.WAITING_FOR_ASSIGNED)
.on(OrderEvent.PAY)
.when(orderConditionHelper.getOrderCheckPayCondition())
.perform(orderActionHelper.getOrderPayAction());
// 外部状态流转 从待分配到部分分配 需要触发部分分配操作
builder.externalTransition()
.from(OrderState.WAITING_FOR_ASSIGNED)
.to(OrderState.ASSIGNED_PART)
.on(OrderEvent.PART_ASSIGNED)
.when(orderConditionHelper.getOrderCheckPartAssignCondition())
.perform(orderActionHelper.getOrderPartAssignAction());
// 内部状态流转 部分分配 需要触发部分分配操作
builder.internalTransition().within(OrderState.ASSIGNED_PART)
.on(OrderEvent.PART_ASSIGNED)
.when(orderConditionHelper.getOrderCheckPartAssignCondition())
.perform(orderActionHelper.getOrderPartAssignAction());
// 外部状态流转 从待分配,部分分配到待发货 需要触发分配操作
builder.externalTransitions()
.fromAmong(OrderState.ASSIGNED_PART, OrderState.WAITING_FOR_ASSIGNED)
.to(OrderState.WAITING_FOR_DELIVER)
.on(OrderEvent.ASSIGNED)
.when(orderConditionHelper.getOrderCheckAssignCondition())
.perform(orderActionHelper.getOrderAssignAction());
// 外部状态流转 从待发货到部分分配 需要触发部分取消分配事件
builder.externalTransition()
.from(OrderState.WAITING_FOR_DELIVER)
.to(OrderState.ASSIGNED_PART)
.on(OrderEvent.PART_CANCEL_ASSIGNED)
.when(orderConditionHelper.getOrderCheckPartCancelAssignedCondition())
.perform(orderActionHelper.getOrderPartCancelAssignAction());
// 外部状态流转 从待发货,部分分配到待分配 需要触发取消分配事件
builder.externalTransitions()
.fromAmong(OrderState.WAITING_FOR_DELIVER, OrderState.ASSIGNED_PART)
.to(OrderState.WAITING_FOR_ASSIGNED)
.on(OrderEvent.CANCEL_ASSIGNED)
.when(orderConditionHelper.getOrderCheckCancelAssignCondition())
.perform(orderActionHelper.getOrderCancelAssignAction());
// 内部状态流转 部分分配 需要触发部分取消分配操作
builder.internalTransition().within(OrderState.ASSIGNED_PART)
.on(OrderEvent.PART_CANCEL_ASSIGNED)
.when(orderConditionHelper.getOrderCheckPartCancelAssignedCondition())
.perform(orderActionHelper.getOrderPartCancelAssignAction());
// 外部状态流转 从待发货到待锁单 需要触发发货事件
builder.externalTransition()
.from(OrderState.WAITING_FOR_DELIVER)
.to(OrderState.WAITING_FOR_LOCK)
.on(OrderEvent.DELIVERY)
.when(orderConditionHelper.getOrderCheckDeliveryCondition())
.perform(orderActionHelper.getOrderDeliveryAction());
// 外部状态流转 从待锁单到已完成 需要触发锁单事件
builder.externalTransition()
.from(OrderState.WAITING_FOR_LOCK)
.to(OrderState.COMPLETE)
.on(OrderEvent.LOCK)
.when(orderConditionHelper.getOrderCheckLockCondition())
.perform(orderActionHelper.getOrderLockAction());
// 外部状态流转 从待分配,部分分配,待发货,待锁单,已完成到待退款 需要触发申请退款事件
builder.externalTransitions()
.fromAmong(OrderState.WAITING_FOR_ASSIGNED, OrderState.ASSIGNED_PART, OrderState.WAITING_FOR_DELIVER,
OrderState.WAITING_FOR_LOCK, OrderState.COMPLETE)
.to(OrderState.WAITING_FOR_REFUND)
.on(OrderEvent.APPLY_FOR_REFUND)
.when(orderConditionHelper.getOrderCheckApplyForRefundedCondition())
.perform(orderActionHelper.getOrderApplyForRefundedAction());
// 外部状态流转 从待退款到已退款 需要触发退款事件
builder.externalTransition()
.from(OrderState.WAITING_FOR_REFUND)
.to(OrderState.REFUNDED)
.on(OrderEvent.REFUND)
.when(orderConditionHelper.getOrderCheckRefundedCondition())
.perform(orderActionHelper.getOrderRefundedAction());
return builder.build(OrderStateMachineIdEnum.CARD_ORDER.getMachineId());
}
}
(9) helper工具类
定义下面两个工具类的目的在于, 在状态机的配置类中就不需要分别注入依赖, 只需要注入这两个Bean就可以, 不想使用这种方式也可以在状态机配置类中分别注入
@Component
@AllArgsConstructor
public class OrderActionHelper {
private final OrderCreateAction orderCreateAction;
private final OrderCancelAction orderCancelAction;
private final OrderPayAction orderPayAction;
private final OrderPartAssignAction orderPartAssignAction;
private final OrderAssignAction orderAssignAction;
private final OrderPartCancelAssignAction orderPartCancelAssignAction;
private final OrderCancelAssignAction orderCancelAssignAction;
private final OrderDeliveryAction orderDeliveryAction;
private final OrderLockAction orderLockAction;
private final OrderApplyForRefundedAction orderApplyForRefundedAction;
private final OrderRefundedAction orderRefundedAction;
public Action<OrderState, OrderEvent, OrderContext> getOrderCreateAction() {
return orderCreateAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderCancelAction() {
return orderCancelAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderPayAction() {
return orderPayAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderPartAssignAction() {
return orderPartAssignAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderAssignAction() {
return orderAssignAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderPartCancelAssignAction() {
return orderPartCancelAssignAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderCancelAssignAction() {
return orderCancelAssignAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderDeliveryAction() {
return orderDeliveryAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderLockAction() {
return orderLockAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderApplyForRefundedAction() {
return orderApplyForRefundedAction;
}
public Action<OrderState, OrderEvent, OrderContext> getOrderRefundedAction() {
return orderRefundedAction;
}
}
@Component
@AllArgsConstructor
public class OrderConditionHelper {
private final OrderCheckCreateCondition orderCheckCreateCondition;
private final OrderCheckCancelCondition orderCheckCancelCondition;
private final OrderCheckPayCondition orderCheckPayCondition;
private final OrderCheckPartAssignCondition orderCheckPartAssignCondition;
private final OrderCheckAssignCondition orderCheckAssignCondition;
private final OrderCheckPartCancelAssignedCondition orderCheckPartCancelAssignedCondition;
private final OrderCheckCancelAssignCondition orderCheckCancelAssignCondition;
private final OrderCheckDeliveryCondition orderCheckDeliveryCondition;
private final OrderCheckLockCondition orderCheckLockCondition;
private final OrderCheckApplyForRefundedCondition orderCheckApplyForRefundedCondition;
private final OrderCheckRefundedCondition orderCheckRefundedCondition;
public Condition<OrderContext> getOrderCheckCreateCondition() {
return orderCheckCreateCondition;
}
public Condition<OrderContext> getOrderCheckCancelCondition() {
return orderCheckCancelCondition;
}
public Condition<OrderContext> getOrderCheckPayCondition() {
return orderCheckPayCondition;
}
public Condition<OrderContext> getOrderCheckPartAssignCondition() {
return orderCheckPartAssignCondition;
}
public Condition<OrderContext> getOrderCheckAssignCondition() {
return orderCheckAssignCondition;
}
public Condition<OrderContext> getOrderCheckPartCancelAssignedCondition() {
return orderCheckPartCancelAssignedCondition;
}
public Condition<OrderContext> getOrderCheckCancelAssignCondition() {
return orderCheckCancelAssignCondition;
}
public Condition<OrderContext> getOrderCheckDeliveryCondition() {
return orderCheckDeliveryCondition;
}
public Condition<OrderContext> getOrderCheckLockCondition() {
return orderCheckLockCondition;
}
public Condition<OrderContext> getOrderCheckApplyForRefundedCondition() {
return orderCheckApplyForRefundedCondition;
}
public Condition<OrderContext> getOrderCheckRefundedCondition() {
return orderCheckRefundedCondition;
}
}
(10)示例触发事件
通过fireEvent()
方法可以触发事件, 如果需要对操作进行加锁, 可以直接对此方法加锁
public void cancelOrder(String orderNo) {
StateMachine<OrderState, OrderEvent, OrderContext> orderStateMachine = StateMachineFactory.get(OrderStateMachineIdEnum.CARD_ORDER.getMachineId());
// 此处使用分布式锁处理
orderUtil.handlerWithLock(orderNo, () -> {
OrderRecordEntity order = getByOrderNo(orderNo);
checkOrderExists(order);
OrderContext orderContext = new OrderContext();
orderContext.setOrderNo(orderNo);
orderContext.setOrder(order);
OrderState orderState = orderStateMachine.fireEvent(OrderState.getInstance(order.getStatus()), OrderEvent.CANCEL_ORDER, orderContext);
if (OrderState.CANCEL.getCode() != orderState.getCode()) {
throw new ServiceException("此状态无法取消订单!");
}
});
}
/**
* 阻塞式获取锁处理订单
* @param orderNo
* @param runnable
*/
public void handlerWithLock(String orderNo, Runnable runnable) {
RLock orderLock = redissonClient.getLock(orderNo);
orderLock.lock();
try {
runnable.run();
} finally {
orderLock.unlock();
}
}