间歇性" IllegalArgumentException:必须设置来源"来自分层弹簧状态机

时间:2016-08-25 15:17:45

标签: java spring spring-statemachine

我正在尝试使用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

1 个答案:

答案 0 :(得分:0)

所以为了实现我想要的,我不应该使用内部转换而是外部转换。这是一个可以在任何子状态上发生的事件,并且应该将该区域返回到初始状态。

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

虽然我也找到了解决内部过渡的方法。如果你想像我一样使用内部转换,那么bug的局限在于你不能在初始状态下拥有正交区域。解决方案是引入两个新状态并从第一个到第二个进行自动转换,第二个是两个正交区域的父状态。

@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);
            }
        };
    }
}