Understanding and Implementing State Machines with Spring Statemachine
This article explains the fundamentals of finite state machines, introduces the four core concepts, demonstrates how to model order state transitions, and provides a complete Spring Statemachine implementation with persistence, event handling, testing, and advanced AOP-based error tracking in Java.
A state machine (finite‑state machine, FSM) is a mathematical model that represents a limited set of states and the transitions between them, driven by events and actions. The article starts by defining state, event, action, and transition, using an automatic door (open/closed) as a simple example.
It then describes how to model an order lifecycle with six elements (start, end, current state, target state, action, condition) and shows a diagram for transitioning from WAIT_PAYMENT to WAIT_DELIVER , WAIT_RECEIVE , and FINISH .
The core of the implementation is Spring Statemachine. The framework provides a flat single‑level state machine, hierarchical state machines, region support, triggers, guards, actions, and persistence adapters. Configuration is done via a @Configuration class that enables the state machine and defines states and transitions:
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter
{
public void configure(StateMachineStateConfigurer
states) throws Exception {
states.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
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);
}
}Database schema for orders is defined with fields such as id , order_code , status , name , price , etc. Sample DDL:
CREATE TABLE `tb_order` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_code` varchar(128) DEFAULT NULL COMMENT '订单编码',
`status` smallint DEFAULT NULL COMMENT '订单状态',
`name` varchar(64) DEFAULT NULL COMMENT '订单名称',
`price` decimal(12,2) DEFAULT NULL COMMENT '价格',
`delete_flag` tinyint 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) DEFAULT NULL COMMENT '创建人',
`update_user_code` varchar(32) DEFAULT NULL COMMENT '更新人',
`version` int NOT NULL DEFAULT '0' COMMENT '版本号',
`remark` varchar(64) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';Enum definitions for states and events:
public enum OrderStatus {
WAIT_PAYMENT(1, "待支付"),
WAIT_DELIVER(2, "待发货"),
WAIT_RECEIVE(3, "待收货"),
FINISH(4, "已完成");
private Integer key;
private String desc;
// getters and utility methods omitted for brevity
}
public enum OrderStatusChangeEvent { PAYED, DELIVERY, RECEIVED; }REST controller exposes endpoints for creating orders and triggering state transitions (pay, deliver, receive). The service layer uses the state machine to send events, restore/persist state, and handle business logic. Event sending is synchronized and logs the thread name:
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);
if (result) {
stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
}
} catch (Exception e) {
log.error("订单操作失败:{}", e);
} finally {
orderStateMachine.stop();
}
return result;
}Listeners ( @OnTransition ) update the order record and can throw exceptions. A problem is that exceptions in listeners are swallowed by the state machine, causing sendEvent to always return true . The article shows how to use the state machine’s ExtendedState to store execution results (1 for success, 0 for failure) and retrieve them after the event.
To avoid repetitive code, an AOP aspect ( @LogResult ) is introduced. It intercepts listener methods, captures the order from the message header, and writes success/failure flags into ExtendedState . The listener methods are then annotated with @LogResult(key = CommonConstants.payTransition) (or deliver/receive keys).
Finally, the article provides Maven dependencies for Spring Statemachine core and Redis persistence, and a sample application.yml configuration for Redis. It also includes testing steps using HTTP calls to verify normal flow and error handling.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.