来自分层 spring 状态机的间歇性 "IllegalArgumentException: Source must be set"

Intermittent "IllegalArgumentException: Source must be set" from hierarchical spring state machine

我正在尝试使用 spring-statemachine 构建分层状态机。它应该有两个正交状态,每个状态代表两个服务的状态。为简单起见,以下代码减少了状态数,但仍然会出现相同的错误。

public enum MachineState {
    BUFF,BUFF_OFFLINE, BUFF_ONLINE,
    CB,CB_OFFLINE,CB_ONLINE
}

public enum MachineEvent {
    BUFF_OFF,BUFF_ON,
    CB_OFF, CB_NORESP, BUFF_NORESP, CB_ON
}

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<MachineState, MachineEvent> {


    @Override
    public void configure(final StateMachineConfigurationConfigurer<MachineState, MachineEvent> config)
            throws Exception {
        config
            .withConfiguration()
            .autoStartup(true);
    }

    @Override
    public void configure(final StateMachineStateConfigurer<MachineState, MachineEvent> states)
            throws Exception {
        states
            .withStates()
                .initial(MachineState.BUFF)
                .and()
                .withStates()
                    .parent(MachineState.BUFF)
                    .initial(MachineState.BUFF_OFFLINE)
                    .state(MachineState.BUFF_ONLINE)
            .and()
            .withStates()
                .initial(MachineState.CB)
                .and()
                .withStates()
                    .parent(MachineState.CB)
                    .initial(MachineState.CB_OFFLINE)
                    .state(MachineState.CB_ONLINE)
            .and()
        ;
    }

    @Override
    public void configure(final StateMachineTransitionConfigurer<MachineState, MachineEvent> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(MachineState.BUFF_OFFLINE).target(MachineState.BUFF_ONLINE)
                .event(MachineEvent.BUFF_ON)
            .and()

            .withExternal()
                .source(MachineState.BUFF_ONLINE).target(MachineState.BUFF_OFFLINE)
                .event(MachineEvent.BUFF_OFF)
            .and()

            .withExternal()
                .source(MachineState.CB_OFFLINE).target(MachineState.CB_ONLINE)
                .event(MachineEvent.CB_ON)
            .and()

            .withExternal()
                .source(MachineState.CB_ONLINE).target(MachineState.CB_OFFLINE)
                .event(MachineEvent.CB_OFF)
            .and()

            .withInternal()
                .source(MachineState.CB)
                .event(MachineEvent.CB_NORESP)
            .and()

            .withInternal()
                .source(MachineState.BUFF)
                .event(MachineEvent.BUFF_NORESP)
            .and()
        ;
    }
}

首先,我是不是配置有问题?

我得到的错误如下

Caused by: java.lang.IllegalArgumentException: Source must be set
    at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.statemachine.transition.AbstractTransition.<init>(AbstractTransition.java:63) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.transition.AbstractInternalTransition.<init>(AbstractInternalTransition.java:35) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.transition.DefaultInternalTransition.<init>(DefaultInternalTransition.java:35) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.config.AbstractStateMachineFactory.buildMachine(AbstractStateMachineFactory.java:704) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:189) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:126) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.statemachine.config.configuration.StateMachineConfiguration$StateMachineDelegatingFactoryBean.afterPropertiesSet(StateMachineConfiguration.java:154) ~[spring-statemachine-core-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]

我调试了应用程序,发现在 spring-statemachine-core AbstractStateMachineFactory buildMachine() 中,stateMap 缺少 CB 和 BUFF 状态之一。最奇怪的部分是哪一个接缝是随机的,有时它实际上包含整个集合,我也不例外。

我尝试删除两个内部转换并调试代码,发现即使 stateMap 不完整(如果我从那个缺失的状态进行转换,它也会失败)实例化后的状态机看起来完全符合我的要求它到那里的所有州。

有什么想法吗?

示例项目https://www.dropbox.com/s/qlarppnma0dq9ai/statemachineerror.tar.gz?dl=0

因此,为了实现我想要的效果,我不应该使用内部过渡,而应该使用外部过渡。这是一个可以在任何子状态上发生的事件,并且应该 return 该区域到初始状态。

.withExternal()
    .source(MachineState.CB).target(MachineState.CB)
    .event(MachineEvent.CB_NORESP)
.and()
.withExternal()
    .source(MachineState.BUFF).target(MachineState.BUFF)
    .event(MachineEvent.BUFF_NORESP)
.and()

虽然我也找到了解决内部过渡的方法。该错误的局限性在于,如果您想像我一样使用内部转换,则在初始状态下不能有正交区域。解决方案是引入两个新状态并从第一个状态自动过渡到第二个状态,并让第二个状态成为两个正交区域的父状态。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<MachineState, MachineEvent> {


    @Override
    public void configure(final StateMachineConfigurationConfigurer<MachineState, MachineEvent> config)
            throws Exception {
        config
            .withConfiguration()
            .autoStartup(true);
    }

    @Override
    public void configure(final StateMachineStateConfigurer<MachineState, MachineEvent> states)
            throws Exception {
        states
            .withStates()
                .initial(MachineState.INITIAL)
                .state(MachineState.INITIAL, init(), null)
                .state(MachineState.PARENT)
                .and()

                // Region 1 (BUFF)
                .withStates()
                    .parent(MachineState.PARENT)
                    .initial(MachineState.BUFF)
                    .and()
                    .withStates()
                        .parent(MachineState.BUFF)
                        .initial(MachineState.BUFF_OFFLINE)
                        .state(MachineState.BUFF_ONLINE)
                .and()

                // Region 2 (CB)
                .withStates()
                    .parent(MachineState.PARENT)
                    .initial(MachineState.CB)
                    .and()
                    .withStates()
                        .parent(MachineState.CB)
                        .initial(MachineState.CB_OFFLINE)
                        .state(MachineState.CB_ONLINE)
                .and()
        ;
    }

    @Override
    public void configure(final StateMachineTransitionConfigurer<MachineState, MachineEvent> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(MachineState.BUFF_OFFLINE).target(MachineState.BUFF_ONLINE)
                .event(MachineEvent.BUFF_ON)
            .and()

            .withExternal()
                .source(MachineState.BUFF_ONLINE).target(MachineState.BUFF_OFFLINE)
                .event(MachineEvent.BUFF_OFF)
            .and()

            .withExternal()
                .source(MachineState.CB_OFFLINE).target(MachineState.CB_ONLINE)
                .event(MachineEvent.CB_ON)
            .and()

            .withExternal()
                .source(MachineState.CB_ONLINE).target(MachineState.CB_OFFLINE)
                .event(MachineEvent.CB_OFF)
            .and()

            .withInternal()
                .source(MachineState.CB)
                .event(MachineEvent.CB_NORESP)
            .and()

            .withInternal()
                .source(MachineState.BUFF)
                .event(MachineEvent.BUFF_NORESP)
            .and()

            .withExternal()
                .source(MachineState.INITIAL).target(MachineState.PARENT)
                .event(MachineEvent.INIT)
            .and()
        ;
    }

    @Bean
    public Action<MachineState, MachineEvent> init() {
        return new Action<MachineState, MachineEvent>() {
            @Override
            public void execute(StateContext<MachineState, MachineEvent> context) {
                context.getStateMachine().sendEvent(MachineEvent.INIT);
            }
        };
    }
}