状态机模式实现

一、状态机概念

1.1 定义

状态机:描述对象在不同状态下的行为以及状态之间的转移规则。

1.2 四要素

要素说明
状态(State)对象的当前状态
事件(Event)触发状态转移的条件
动作(Action)状态转移时执行的操作
转移(Transition)从一个状态到另一个状态的过程

1.3 状态机图示

订单状态机:
┌─────────────┐     支付成功      ┌─────────────┐     开始制作      ┌─────────────┐
│  待支付     │ ───────────────> │   制作中    │ ───────────────> │   已完成    │
│ PENDING     │                  │  MAKING     │                  │  COMPLETED  │
└──────┬──────┘                  └──────┬──────┘                  └─────────────┘
       │                                 │
       │ 取消订单                        │ 取消订单
       ↓                                 ↓
┌─────────────┐                  ┌─────────────┐
│   已取消    │                  │   已取消    │
│ CANCELLED  │                  │ CANCELLED  │
└─────────────┘                  └─────────────┘

二、简单状态机实现

2.1 枚举方式

public enum OrderStatus {
    PENDING_PAYMENT("待支付"),
    MAKING("制作中"),
    COMPLETED("已完成"),
    CANCELLED("已取消");
    
    private final String description;
    
    OrderStatus(String description) {
        this.description = description;
    }
    
    // 状态转移映射
    private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED_TRANSITIONS = Map.of(
        PENDING_PAYMENT, Set.of(MAKING, CANCELLED),
        MAKING, Set.of(COMPLETED, CANCELLED),
        COMPLETED, Set.of(),  // 终态,无转移
        CANCELLED, Set.of()   // 终态,无转移
    );
    
    // 验证状态转移是否合法
    public boolean canTransitionTo(OrderStatus target) {
        return ALLOWED_TRANSITIONS.get(this).contains(target);
    }
}

2.2 状态机服务

@Service
public class OrderStateMachineService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional
    public boolean transition(Long orderId, OrderStatus targetStatus) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        
        // 验证状态转移合法性
        if (!order.getStatus().canTransitionTo(targetStatus)) {
            throw new IllegalStateException(
                String.format("无法从%s转移到%s", order.getStatus(), targetStatus)
            );
        }
        
        // 使用乐观锁更新状态
        int updated = orderRepository.updateStatus(
            orderId, 
            order.getStatus(), 
            targetStatus
        );
        
        return updated > 0;
    }
}

2.3 数据库更新(乐观锁)

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    @Modifying
    @Query("UPDATE Order o SET o.status = :newStatus, o.version = o.version + 1 " +
           "WHERE o.id = :orderId AND o.status = :currentStatus AND o.version = :version")
    int updateStatusWithVersion(
        @Param("orderId") Long orderId,
        @Param("currentStatus") OrderStatus currentStatus,
        @Param("newStatus") OrderStatus newStatus,
        @Param("version") int version
    );
}

三、使用状态机框架

3.1 Spring Statemachine

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
    
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
        states
            .withStates()
                .initial(OrderStatus.PENDING_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class))
                .end(OrderStatus.COMPLETED)
                .end(OrderStatus.CANCELLED);
    }
    
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
        transitions
            .withExternal()
                .source(OrderStatus.PENDING_PAYMENT).target(OrderStatus.MAKING)
                .event(OrderEvent.PAY_SUCCESS)
                .action(paySuccessAction())
            .and()
            .withExternal()
                .source(OrderStatus.MAKING).target(OrderStatus.COMPLETED)
                .event(OrderEvent.COMPLETE)
                .action(completeAction())
            .and()
            .withExternal()
                .source(OrderStatus.PENDING_PAYMENT).target(OrderStatus.CANCELLED)
                .event(OrderEvent.CANCEL)
                .action(cancelAction())
            .and()
            .withExternal()
                .source(OrderStatus.MAKING).target(OrderStatus.CANCELLED)
                .event(OrderEvent.CANCEL)
                .action(cancelAction());
    }
    
    @Bean
    public Action<OrderStatus, OrderEvent> paySuccessAction() {
        return context -> System.out.println("支付成功,开始制作");
    }
    
    @Bean
    public Action<OrderStatus, OrderEvent> completeAction() {
        return context -> System.out.println("订单完成");
    }
    
    @Bean
    public Action<OrderStatus, OrderEvent> cancelAction() {
        return context -> System.out.println("订单已取消");
    }
}

3.2 事件枚举

public enum OrderEvent {
    PAY_SUCCESS("支付成功"),
    COMPLETE("完成"),
    CANCEL("取消");
    
    private final String description;
    
    OrderEvent(String description) {
        this.description = description;
    }
}

四、分布式场景下的状态机

4.1 数据库层面的并发控制

-- 状态更新必须验证当前状态
UPDATE orders 
SET status = 'MAKING', version = version + 1
WHERE id = 123 
  AND status = 'PENDING_PAYMENT'
  AND version = 1;

4.2 分布式锁保护

@Service
public class DistributedStateMachineService {
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private OrderStateMachineService stateMachineService;
    
    public boolean safeTransition(Long orderId, OrderStatus targetStatus) {
        String lockKey = "order:state:" + orderId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                return stateMachineService.transition(orderId, targetStatus);
            }
            throw new RuntimeException("操作过于频繁,请稍后重试");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("操作被中断");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

五、状态持久化

5.1 状态机状态持久化

@Service
public class StateMachinePersistenceService {
    @Autowired
    private StateMachine<OrderStatus, OrderEvent> stateMachine;
    @Autowired
    private OrderRepository orderRepository;
    
    public void restoreState(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        stateMachine.getStateMachineAccessor()
            .doWithAllRegions(access -> {
                access.resetStateMachine(new DefaultStateMachineContext<>(
                    order.getStatus(), 
                    null, 
                    null, 
                    null
                ));
            });
    }
    
    public void saveState(Long orderId) {
        OrderStatus currentState = stateMachine.getState().getId();
        orderRepository.updateStatusOnly(orderId, currentState);
    }
}

六、面试要点

6.1 核心概念

概念说明
状态机状态转移规则的集合
状态转移从一个状态到另一个状态的变化
终态不再转移的状态
乐观锁通过版本号实现并发控制

6.2 常见面试问题

Q:为什么需要状态机?

A:状态机确保状态转移的合法性,防止非法状态跳转,使业务逻辑更清晰。

Q:如何防止并发状态修改?

A:使用数据库乐观锁(版本号)或分布式锁保证原子性。

Q:什么时候使用框架实现状态机?

A:状态转移规则复杂、需要持久化状态、需要监听器扩展时使用框架。


参考链接