如何基于Spring State Machine实现订单单状态映射多个status?
Hey there! Let's break down how to handle this scenario where a single logical STATE maps to multiple concrete status values in Spring State Machine. This is a super common use case when you need to track granular sub-states under broader categories, and there are two solid approaches to implement this effectively.
Approach 1: Use Extended State Attributes for Logical Grouping
The simplest way is to treat each status as the actual state in your state machine, and attach a logical STATE attribute to each status. This lets you group granular states under a broader category while keeping your state machine configuration straightforward.
Step 1: Define Enums with Logical State Mapping
First, create an enum for your logical top-level states, then another enum for the concrete status values that reference their parent logical state:
// Logical top-level states public enum LogicalOrderState { NEW, PROCESSING, COMPLETE } // Concrete status values (actual states in the machine) public enum OrderStatus { PENDING(LogicalOrderState.NEW), PAYMENT_PENDING(LogicalOrderState.NEW), PROCESSING_NEW(LogicalOrderState.PROCESSING), PROCESSING_COMPLETE(LogicalOrderState.PROCESSING), COMPLETE_SHIPPED(LogicalOrderState.COMPLETE), DELIVERED(LogicalOrderState.COMPLETE); private final LogicalOrderState logicalState; OrderStatus(LogicalOrderState logicalState) { this.logicalState = logicalState; } public LogicalOrderState getLogicalState() { return logicalState; } }
Step 2: Configure the State Machine
Use OrderStatus as your state type, and configure transitions between the concrete status values. When you need to check the logical state later, just call getLogicalState() on the current status:
@Configuration @EnableStateMachine public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> { @Override public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception { states .withStates() .initial(OrderStatus.PENDING) .states(EnumSet.allOf(OrderStatus.class)); } @Override public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception { transitions // Transition within NEW logical state .withExternal() .source(OrderStatus.PENDING) .target(OrderStatus.PAYMENT_PENDING) .event(OrderEvent.INITIATE_PAYMENT) // Transition from NEW to PROCESSING .withExternal() .source(OrderStatus.PAYMENT_PENDING) .target(OrderStatus.PROCESSING_NEW) .event(OrderEvent.PAYMENT_SUCCESS) // Add other transitions as needed for your workflow .and() .withExternal() .source(OrderStatus.PROCESSING_NEW) .target(OrderStatus.PROCESSING_COMPLETE) .event(OrderEvent.FINISH_PROCESSING) .and() .withExternal() .source(OrderStatus.PROCESSING_COMPLETE) .target(OrderStatus.COMPLETE_SHIPPED) .event(OrderEvent.SHIP_ORDER) .and() .withExternal() .source(OrderStatus.COMPLETE_SHIPPED) .target(OrderStatus.DELIVERED) .event(OrderEvent.MARK_DELIVERED); } }
Usage in Business Logic
When you need to check the logical state of an order:
// Get current status from state machine OrderStatus currentStatus = stateMachine.getState().getId(); // Get the logical parent state LogicalOrderState logicalState = currentStatus.getLogicalState(); if (logicalState == LogicalOrderState.NEW) { // Handle actions for all NEW-related statuses }
Approach 2: Hierarchical State Machine (Substates)
If you want to leverage Spring State Machine's hierarchical state features (like shared entry/exit actions, or transitioning from any substate of a parent), you can define the logical STATE as parent states and the status values as their substates.
Step 1: Define State Enums
Keep separate enums for parent logical states and concrete substates:
public enum LogicalOrderState { NEW, PROCESSING, COMPLETE } public enum OrderStatus { PENDING, PAYMENT_PENDING, PROCESSING_NEW, PROCESSING_COMPLETE, COMPLETE_SHIPPED, DELIVERED }
Step 2: Configure Hierarchical States
In your state machine config, assign each substate to its parent logical state. You can also define transitions from parent states (which apply to all their substates):
@Configuration @EnableStateMachine public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<Enum<?>, OrderEvent> { @Override public void configure(StateMachineStateConfigurer<Enum<?>, OrderEvent> states) throws Exception { states .withStates() // Set initial concrete state .initial(OrderStatus.PENDING) // Define parent logical states .state(LogicalOrderState.NEW) .state(LogicalOrderState.PROCESSING) .state(LogicalOrderState.COMPLETE) // Assign substates to parents .and() .withStates() .parent(LogicalOrderState.NEW) .states(EnumSet.of(OrderStatus.PENDING, OrderStatus.PAYMENT_PENDING)) .and() .withStates() .parent(LogicalOrderState.PROCESSING) .states(EnumSet.of(OrderStatus.PROCESSING_NEW, OrderStatus.PROCESSING_COMPLETE)) .and() .withStates() .parent(LogicalOrderState.COMPLETE) .states(EnumSet.of(OrderStatus.COMPLETE_SHIPPED, OrderStatus.DELIVERED)); } @Override public void configure(StateMachineTransitionConfigurer<Enum<?>, OrderEvent> transitions) throws Exception { transitions // Transition within NEW substate .withExternal() .source(OrderStatus.PENDING) .target(OrderStatus.PAYMENT_PENDING) .event(OrderEvent.INITIATE_PAYMENT) // Transition from ANY NEW substate to PROCESSING_NEW .withExternal() .source(LogicalOrderState.NEW) .target(OrderStatus.PROCESSING_NEW) .event(OrderEvent.PAYMENT_SUCCESS) // Transition within PROCESSING substate .withExternal() .source(OrderStatus.PROCESSING_NEW) .target(OrderStatus.PROCESSING_COMPLETE) .event(OrderEvent.FINISH_PROCESSING) // Transition from ANY PROCESSING substate to COMPLETE_SHIPPED .withExternal() .source(LogicalOrderState.PROCESSING) .target(OrderStatus.COMPLETE_SHIPPED) .event(OrderEvent.SHIP_ORDER) // Transition within COMPLETE substate .withExternal() .source(OrderStatus.COMPLETE_SHIPPED) .target(OrderStatus.DELIVERED) .event(OrderEvent.MARK_DELIVERED); } // Listener to track state changes and logical state mapping @Bean public StateMachineListener<Enum<?>, OrderEvent> stateMachineListener() { return new StateMachineListenerAdapter<>() { @Override public void stateChanged(State<Enum<?>, OrderEvent> from, State<Enum<?>, OrderEvent> to) { if (to != null && to.getId() instanceof OrderStatus status) { LogicalOrderState logicalState = mapStatusToLogicalState(status); System.out.printf("Current status: %s | Logical state: %s%n", status, logicalState); } } }; } private LogicalOrderState mapStatusToLogicalState(OrderStatus status) { return switch (status) { case PENDING, PAYMENT_PENDING -> LogicalOrderState.NEW; case PROCESSING_NEW, PROCESSING_COMPLETE -> LogicalOrderState.PROCESSING; case COMPLETE_SHIPPED, DELIVERED -> LogicalOrderState.COMPLETE; }; } }
Key Benefits of This Approach
- Shared Behavior: You can add entry/exit actions to parent states that apply to all their substates (e.g., log something whenever entering the
PROCESSINGlogical state). - Simplified Transitions: Transitioning from a parent state means any of its substates can trigger the transition, reducing redundant config.
Which Approach Should You Choose?
- Use Approach 1 if you just need logical grouping without hierarchical state features—it's simpler and easier to maintain.
- Use Approach 2 if you need to leverage hierarchical state behaviors like shared actions or bulk transitions.
内容的提问来源于stack exchange,提问作者RahulAggarwal

