我正在尝试使用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
答案 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);
}
};
}
}