Spring State Machine到达结束状态后再次获取回退至初始状态求助
Spring State Machine结束状态回退到初始状态问题排查与解决建议
问题场景
在使用Spring State Machine时遇到一个问题:当状态机到达结束状态(如FINISHED)后,第二次通过stateMachineService.acquireStateMachine获取状态机时,状态并未保留在结束状态,反而回退到初始状态CREATED,导致后续状态校验失败。
校验方法代码如下:
public void validateSessionState(Session session, SessionState state) { StateMachine<SessionState, SessionEvent> stateMachine = stateMachineService.acquireStateMachine(session.getId().toString()); if (!stateMachine.getState().getId().equals(state)) { throw new IllegalStateException( "Session with id: " + session.getId() + " is not " + state + " but " + stateMachine.getState().getId()); } }
首次调用该方法时校验正常,但后续调用时校验失败,且状态机的CREATED状态被持久化。
状态机配置类代码:
@Configuration @EnableStateMachineFactory @RequiredArgsConstructor public class SessionStateMachineConfig extends EnumStateMachineConfigurerAdapter<SessionState, SessionEvent> { private final StateMachineRuntimePersister<SessionState, SessionEvent, String> persister; @Override public void configure(StateMachineStateConfigurer<SessionState, SessionEvent> states) throws Exception { states .withStates() .initial(SessionState.CREATED) .end(SessionState.FINISHED) .end(SessionState.EXPIRED) .states(EnumSet.allOf(SessionState.class)); } @Override public void configure( final StateMachineConfigurationConfigurer<SessionState, SessionEvent> config) throws Exception { config.withConfiguration().autoStartup(false); config.withPersistence().runtimePersister(persister); } @Override public void configure(StateMachineTransitionConfigurer<SessionState, SessionEvent> transitions) throws Exception { transitions .withExternal() .source(SessionState.CREATED) .target(SessionState.ACTIVATED) .event(SessionEvent.ACTIVATE) .action(loggingAction()) .and() .withExternal() .source(SessionState.CREATED) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.ACTIVATED) .target(SessionState.ANSWERING_COMMONS) .event(SessionEvent.GET_COMMONS) .action(loggingAction()) .and() .withExternal() .source(SessionState.ACTIVATED) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.ANSWERING_COMMONS) .target(SessionState.ANSWERING_SKILL_SET) .event(SessionEvent.GET_SKILL_SET) .action(loggingAction()) .and() .withExternal() .source(SessionState.ANSWERING_COMMONS) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.ANSWERING_SKILL_SET) .target(SessionState.PROCESSING_QUIZ) .event(SessionEvent.GENERATE) .action(loggingAction()) .and() .withExternal() .source(SessionState.ANSWERING_SKILL_SET) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.PROCESSING_QUIZ) .target(SessionState.IN_PROGRESS) .event(SessionEvent.START) .action(loggingAction()) .and() .withExternal() .source(SessionState.PROCESSING_QUIZ) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.IN_PROGRESS) .target(SessionState.PROCESSING_RESULTS) .event(SessionEvent.SUBMIT) .action(loggingAction()) .and() .withExternal() .source(SessionState.IN_PROGRESS) .target(SessionState.EXPIRED) .event(SessionEvent.EXPIRE) .action(loggingAction()) .and() .withExternal() .source(SessionState.PROCESSING_RESULTS) .target(SessionState.FINISHED) .event(SessionEvent.FINISH) .action(loggingAction()); } @Bean public StateMachineService<SessionState, SessionEvent> stateMachineService( final StateMachineFactory<SessionState, SessionEvent> sessionStateMachineFactory, final StateMachineRuntimePersister<SessionState, SessionEvent, String> sessionsStateMachinePersist) { return new DefaultStateMachineService<>( sessionStateMachineFactory, sessionsStateMachinePersist); } @Bean public Action<SessionState, SessionEvent> loggingAction() { return new LoggingAction(); } }
原因分析
- Spring State Machine默认对结束状态的处理逻辑:当状态机进入结束状态后,会标记自身为
completed状态。DefaultStateMachineService在获取状态机实例时,若检测到原实例已完成,会重新创建新的状态机实例,而非从持久化存储中恢复结束状态。 - 持久化逻辑缺失:如果
StateMachineRuntimePersister的实现仅存储了状态ID,未保存状态机的completed标记或结束状态的元数据,恢复时无法识别当前处于结束状态,会默认初始化到初始状态。
解决方案建议
1. 完善持久化逻辑,保留结束状态元数据
确保StateMachineRuntimePersister的实现不仅存储当前状态ID,还需保存状态机的completed状态。在恢复状态机时,先判断是否处于completed状态,若已完成则直接恢复到对应的结束状态,而非初始化到初始状态。
2. 配置状态机的结束状态恢复策略
修改状态机配置,启用停止状态的重启支持,让状态机在恢复时保留结束状态:
@Override public void configure(StateMachineConfigurationConfigurer<SessionState, SessionEvent> config) throws Exception { config.withConfiguration() .autoStartup(false) .enableStateMachineStoppedRestart(true); // 启用停止状态的重启支持 config.withPersistence().runtimePersister(persister); }
3. 手动校验并恢复结束状态
修改校验方法,在获取状态机后先从持久化存储中读取原始状态,若为结束状态则手动恢复:
public void validateSessionState(Session session, SessionState state) { String machineId = session.getId().toString(); // 从持久化存储读取原始上下文 StateMachineContext<SessionState, SessionEvent> context = persister.read(machineId); StateMachine<SessionState, SessionEvent> stateMachine = stateMachineService.acquireStateMachine(machineId); if (context != null && context.getState().getId().isEndState()) { // 恢复结束状态 stateMachine.stop(); stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> { accessor.resetStateMachine(context); }); stateMachine.start(); } if (!stateMachine.getState().getId().equals(state)) { throw new IllegalStateException( "Session with id: " + session.getId() + " is not " + state + " but " + stateMachine.getState().getId()); } }
4. 业务层面优化
当状态机进入结束状态后,直接通过持久化存储的状态进行校验,不再重复获取状态机实例,避免不必要的状态恢复操作。
内容的提问来源于stack exchange,提问作者Artyom Sushchenko
相关产品推荐
相关产品推荐

