Understanding Finite State Machines and Implementing Spring Statemachine for Order Processing
This article explains the fundamentals of finite state machines, introduces the four core concepts of state, event, action, and transition, and demonstrates how to model and persist order workflows using Spring Statemachine with Java code examples, database schema, and AOP-based error handling.
1. What Is a State Machine
A state machine (finite-state machine) is a mathematical model that represents a system with a limited number of states and transitions triggered by events, such as an automatic door switching between open and closed.
1.1 What Is a State
Real‑world objects have distinct states; for example, an order can be WAIT_PAYMENT, WAIT_DELIVER, WAIT_RECEIVE, or FINISH.
1.2 Four Core Concepts
State : the condition of the object (e.g., open, closed).
Event : the trigger that causes a transition (e.g., pressing the open button).
Action : the operation performed when an event occurs (typically a method call).
Transition : the change from one state to another.
1.3 Finite‑State Machine (FSM)
An FSM consists of a set of states, an initial state, input events, and a transition function that determines the next state based on the current state and event.
2. State Machine Diagram
When modeling a business process such as order handling, six elements are needed: start, end, current state, target state, action, and condition.
3. Spring Statemachine
3.1 Overview
Spring Statemachine provides a framework for using state‑machine concepts inside Spring applications, offering flat or hierarchical machines, region support, triggers, guards, actions, and integration with Spring IoC.
3.2 Quick Start
Database schema for an order table:
CREATE TABLE `tb_order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_code` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单编码',
`status` smallint(3) DEFAULT NULL COMMENT '订单状态',
`name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单名称',
`price` decimal(12,2) DEFAULT NULL COMMENT '价格',
`delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标记,0未删除 1已删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间',
`create_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',
`update_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',
`version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
`remark` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';Enum definitions for state and events:
public enum OrderStatus {
WAIT_PAYMENT(1, "待支付"),
WAIT_DELIVER(2, "待发货"),
WAIT_RECEIVE(3, "待收货"),
FINISH(4, "已完成");
private Integer key;
private String desc;
// constructors, getters, and utility methods omitted for brevity
} public enum OrderStatusChangeEvent {
PAYED, DELIVERY, RECEIVED;
}Configuration of the state machine:
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
states.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
@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);
}
}Persistence beans (in‑memory and Redis) are defined to store the state machine context between requests.
@Bean(name = "stateMachineMemPersister")
public static StateMachinePersister getPersister() {
return new DefaultStateMachinePersister(new StateMachinePersist() {
private Map map = new HashMap();
@Override
public void write(StateMachineContext context, Object contextObj) throws Exception {
map.put(contextObj, context);
}
@Override
public StateMachineContext read(Object contextObj) throws Exception {
return (StateMachineContext) map.get(contextObj);
}
});
}Controller and service layers expose REST endpoints for creating orders, paying, delivering, and receiving, while the service uses a synchronized sendEvent method to start the machine, restore its state, send the event, and persist the new state only when the transition succeeds.
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource private OrderService orderService;
@RequestMapping("/create") public String create(@RequestBody Order order) { orderService.create(order); return "success"; }
@RequestMapping("/pay") public String pay(@RequestParam("id") Long id) { orderService.pay(id); return "success"; }
// other endpoints omitted for brevity
}The listener class updates the database after each successful transition and demonstrates how exceptions inside transition methods are swallowed by the state machine, requiring explicit handling via the extended state or AOP.
@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl {
@Resource private OrderMapper orderMapper;
@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
@Transactional
@LogResult(key = CommonConstants.payTransition)
public void payTransition(Message<OrderStatusChangeEvent> message) {
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
if (Objects.equals(order.getName(), "A")) {
throw new RuntimeException("执行业务异常");
}
}
// deliverTransition and receiveTransition omitted for brevity
}Because the state machine returns true even when a transition throws an exception, the article proposes storing execution results in the extended state and checking them before persisting, or using a custom @LogResult AOP annotation to record success (1) or failure (0).
3.4 Problems and Solutions
Issues such as exceptions being consumed by the state machine and the need to differentiate event types are addressed by extending the state with result variables and by creating a generic AOP aspect that captures the outcome of transition methods.
Overall, the guide provides a complete, production‑ready example of modeling order lifecycle management with Spring Statemachine, covering schema design, dependency configuration, state definitions, transition logic, persistence strategies, testing, and advanced error‑handling techniques.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
