Mastering Order Workflow with Spring Statemachine: A Step‑by‑Step Guide
This tutorial shows how to replace bulky if‑else or switch logic with Spring Statemachine in a Spring Boot application, covering dependency setup, state and event enums, machine configuration, persistence, service implementation, controller endpoints, and advanced conditional transitions for loan processing.
Background
When handling loan order status transitions, using plain if‑else or switch statements quickly becomes hard to maintain. A state machine offers a structured, maintainable way to manage these transitions.
1. Add Spring Statemachine Dependency
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>4.0.0</version>
</dependency>2. Define Order Status Enum
public enum OrderStatusEnum {
// 待审核
APPROVE_PENDING,
// 审核中
APPROVE_ING,
// 审核失败
APPROVE_FAILED,
// 审核成功
APPROVE_SUCCESS
}3. Define Order Event Enum
public enum OrderEvent {
// 开始审核
APPROVE_START,
// 审核通过
APPROVE_SUCCESS,
// 审核失败
APPROVE_FAILED
}4. Configure the State Machine
@Configuration
@EnableStateMachine(name = "OrderStateMachine")
@Slf4j
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatusEnum, OrderEvent> {
@Resource
private OrderMapper orderMapper;
/** Configure states */
@Override
public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEvent> states) throws Exception {
states.withStates()
.initial(OrderStatusEnum.APPROVE_PENDING) // 初始状态
.states(EnumSet.allOf(OrderStatusEnum.class));
}
/** Configure transitions */
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception {
transitions
// 待审核 -> 审核中
.withExternal().source(OrderStatusEnum.APPROVE_PENDING).target(OrderStatusEnum.APPROVE_ING).event(OrderEvent.APPROVE_START).and()
// 审核中 -> 审核失败
.withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_FAILED).event(OrderEvent.APPROVE_FAILED).and()
// 审核中 -> 审核成功
.withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_SUCCESS).event(OrderEvent.APPROVE_SUCCESS);
}
/** Persistence bean */
@Bean
public DefaultStateMachinePersister<OrderStatusEnum, OrderEvent, BizOrder> persister() {
return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEvent, BizOrder>() {
@Override
public void write(StateMachineContext<OrderStatusEnum, OrderEvent> context, BizOrder order) throws Exception {
OrderStatusEnum orderStatus = context.getState();
log.info("订单状态持久化,订单ID:{},目标状态:{}", order.getId(), orderStatus);
orderMapper.updateOrderStatus(order.getId(), orderStatus);
}
@Override
public StateMachineContext<OrderStatusEnum, OrderEvent> read(BizOrder order) throws Exception {
log.info("恢复订单状态机状态");
return new DefaultStateMachineContext<>(order.getStatus(), null, null, null);
}
});
}
}5. Service Interface for State Changes
public interface BizOrderStatusService {
/**
* 通用状态变更处理器
*/
void eventHandler(Long orderId, OrderEvent event);
}6. Service Implementation
@Service
public class BizOrderStatusServiceImpl implements BizOrderStatusService {
@Resource
private OrderMapper orderMapper;
@Resource
private StateMachine<OrderStatusEnum, OrderEvent> orderStateMachine;
@Resource
private StateMachinePersister<OrderStatusEnum, OrderEvent, BizOrder> persister;
@Override
public void eventHandler(Long orderId, OrderEvent event) {
BizOrder order = orderMapper.getOrderById(orderId);
Assert.notNull(order, "订单不存在");
StateMachineParam param = new StateMachineParam();
param.setBizOrder(order);
Message<OrderEvent> message = MessageBuilder.withPayload(event).build();
if (!sendEvent(message, param)) {
throw new ApplicationBizException("订单状态流转异常");
}
}
/**
* 发送事件(示例中使用 synchronized,仅示意)
*/
private synchronized boolean sendEvent(Message<OrderEvent> message, StateMachineParam param) {
boolean result = false;
try {
orderStateMachine.start();
persister.restore(orderStateMachine, param.getBizOrder());
orderStateMachine.getExtendedState().getVariables().put("param", param);
result = orderStateMachine.sendEvent(message);
persister.persist(orderStateMachine, param.getBizOrder());
} catch (Exception e) {
e.printStackTrace();
} finally {
orderStateMachine.stop();
}
return result;
}
}7. Controller Endpoints
@RestController
@RequiredArgsConstructor
public class ApproveController {
private final OrderStatusService orderStatusService;
/** 将订单状态改为审核中 */
@PostMapping("/start")
public void start(Long orderId) {
orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_START);
}
/** 将订单状态改为审核成功 */
@PostMapping("/approveSuccess")
public void approveSuccess(Long orderId) {
orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_SUCCESS);
}
/** 将订单状态改为审核失败 */
@PostMapping("/approveFailed")
public void approveFailed(Long orderId) {
orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_FAILED);
}
}8. Extending the Model for Loan Scenarios
To handle loan outcomes, add two new status values and a new event:
public enum OrderStatusEnum {
// ... existing statuses ...
LOAN_SUCCESS,
PARTIALLY_LOAN_SUCCESS;
} public enum OrderEvent {
// ... existing events ...
LOAN;
}9. Conditional Transitions with Guards
Configure the state machine so that the LOAN event can lead to either LOAN_SUCCESS or PARTIALLY_LOAN_SUCCESS based on the order’s amounts.
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception {
transitions
// existing transitions ...
.withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.LOAN_SUCCESS)
.event(OrderEvent.LOAN).guard(guardForLoanSuccessByLoan()).and()
.withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.PARTIALLY_LOAN_SUCCESS)
.event(OrderEvent.LOAN).guard(guardForPartiallyLoanSuccessByLoan());
}
@Bean
public Guard<OrderStatusEnum, OrderEvent> guardForLoanSuccessByLoan() {
return context -> {
StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param");
BizOrder order = param.getBizOrder();
return order.getApplyAmount().compareTo(order.getLoanAmount()) == 0;
};
}
@Bean
public Guard<OrderStatusEnum, OrderEvent> guardForPartiallyLoanSuccessByLoan() {
return context -> {
StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param");
BizOrder order = param.getBizOrder();
return order.getApplyAmount().compareTo(order.getLoanAmount()) < 0;
};
}With these configurations, the state machine can route the same LOAN event to different target states depending on business conditions, enabling flexible workflow handling.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
