Understanding Finite State Machines and Spring Statemachine for Order Processing in Java
This article explains the fundamentals of finite state machines, introduces the four core concepts of state, event, action, and transition, demonstrates how to model an order lifecycle with a state‑machine diagram, and provides a complete Spring Statemachine implementation—including enums, configuration, persistence (memory and Redis), listeners, testing, and solutions to common exception‑handling issues—complete with runnable Java code examples.
The article introduces finite state machines (FSM) as a mathematical model that describes a limited set of states and transitions, using a simple automatic door example to illustrate the concepts of state, event, action, and transition.
1.1 What is State
Real‑world objects have distinct states, such as an automatic door being open or closed . A state machine abstracts these into a finite set of possible states.
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 (e.g., opening the door).
Transition : The change from one state to another (e.g., the opening process).
1.3 Finite State Machine
A FSM consists of a set of states, an initial state, inputs, and a transition function that determines the next state based on the current state and input.
2. State Machine Diagram
The article models an order lifecycle with the following states: WAIT_PAYMENT , WAIT_DELIVER , WAIT_RECEIVE , and FINISH . It describes the six elements needed to draw the diagram: start, end, current state, target state, action, and condition.
3. Spring Statemachine
Spring Statemachine is a framework that brings state‑machine concepts into Spring applications. Its main features include simple flat state machines, hierarchical structures, regions, triggers, guards, actions, type‑safe adapters, a generator pattern, Zookeeper‑based distributed state machines, event listeners, UML modeling, persistent storage, and Spring IOC integration.
3.1 Overview
Spring Statemachine ensures consistent behavior, simplifies debugging, and allows external interaction via events, listeners, or state queries.
3.2 Quick Start
SQL for the tb_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 ON UPDATE 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='订单表';Sample data insertion is also provided.
3.3 Define Enums and Configuration
Order status enum:
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.");
}
}Event enum:
public enum OrderStatusChangeEvent { PAYED, DELIVERY, RECEIVED; }State machine configuration:
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter
{
@Override
public void configure(StateMachineStateConfigurer
states) throws Exception {
states.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer
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);
}
}3.4 Persistence
Memory persister bean:
@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);
}
});
}Redis persister configuration (dependency and YAML snippet) is also shown, followed by a bean that creates a RedisStateMachinePersister .
3.5 Listener and Transitions
Listener class updates the order record on each transition:
@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl {
@Resource private OrderMapper orderMapper;
@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
public void payTransition(Message
message) {
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
}
@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
public void deliverTransition(Message
message) {
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
orderMapper.updateById(order);
}
@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
public void receiveTransition(Message
message) {
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.FINISH.getKey());
orderMapper.updateById(order);
}
}3.6 Testing and Verification
Steps to create an order, pay, deliver, and receive via HTTP endpoints are listed, along with expected behavior when repeating a payment (an error is returned).
3.7 Problems and Solutions
The article points out that stateMachine.sendEvent always returns true even when a listener throws an exception, making error detection difficult. It proposes storing execution results in the state machine’s extended state, using AOP to capture success/failure, and adjusting the sendEvent method to persist only when the business logic succeeds.
Sample AOP annotation and aspect code are provided to record the result of each transition.
Overall, the article offers a complete guide to building, persisting, and debugging a Spring‑based finite state machine for order management.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.