Spring-statemachine fork一个region后不能进入join状态的问题
Spring-statemachine版本:当前最新的1.2.3.RELEASE版本
发现fork多个Region时,子状态全部完成后能够进入join状态。但是如果fork一个Region时Region完成后并不能进入join状态。状态机是StateMachineBuilder.builder()构建的。
然后debug源码很久,发现状态机构建的逻辑是这样的:
org.springframework.statemachine.config.AbstractStateMachineFactory
public StateMachine<S, E> getStateMachine(UUID uuid, String machineId) {
...
Collection<StateData<S, E>> stateDatas = popSameParents(stateStack);
// stateDatas代表一个区域(Region。拥有相同的父状态)的状态集合
int initialCount = getInitialCount(stateDatas); //获得一个region的初始化状态的数目
Collection<Collection<StateData<S, E>>> regionsStateDatas = splitIntoRegions(stateDatas);
Collection<TransitionData<S, E>> transitionsData = getTransitionData(iterator.hasNext(), stateDatas, stateMachineModel);
if (initialCount > 1) { // 判断初始化状态数大于1才会通过RegionState创建子状态机
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
machine = buildMachine(machineMap, stateMap, holderMap, regionStateDatas, transitionsData, resolveBeanFactory(stateMachineModel),
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel),
resolveTaskScheduler(stateMachineModel), machineId, null, stateMachineModel);
regionStack.push(new MachineStackItem<S, E>(machine));
}
Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
while (!regionStack.isEmpty()) {
MachineStackItem<S, E> pop = regionStack.pop();
regions.add(pop.machine);
}
S parent = (S)peek.getParent();
// 创建类型为RegionState的状态
RegionState<S, E> rstate = buildRegionStateInternal(parent, regions, null, stateData != null ? stateData.getEntryActions() : null,
stateData != null ? stateData.getExitActions() : null, new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL));
if (stateData != null) {
stateMap.put(stateData.getState(), rstate);
} else {
// TODO: don't like that we create a last machine here
Collection<State<S, E>> states = new ArrayList<State<S, E>>();
states.add(rstate);
Transition<S, E> initialTransition = new InitialTransition<S, E>(rstate);
// 把RegionState转变成ObjectStateMachine子状态机
StateMachine<S, E> m = buildStateMachineInternal(states, new ArrayList<Transition<S, E>>(), rstate, initialTransition,
null, defaultExtendedState, null, contextEvents, resolveBeanFactory(stateMachineModel), resolveTaskExecutor(stateMachineModel),
resolveTaskScheduler(stateMachineModel), beanName,
machineId != null ? machineId : stateMachineModel.getConfigurationData().getMachineId(),
uuid, stateMachineModel);
machine = m;
}
} else { // 如果没有初始化状态或只有一个初始化状态,就构建一个普通的ObjectStateMachine子状态机
machine = buildMachine(machineMap, stateMap, holderMap, stateDatas, transitionsData, resolveBeanFactory(stateMachineModel), contextEvents,
defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel), resolveTaskScheduler(stateMachineModel),
machineId, uuid, stateMachineModel);
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
machineMap.put(peek.getParent(), machine);
}
}
...
}
从上面的源码可以看出:当子状态机只有一个初始化状态时,不会通过RegionState去创建子状态机。
在同一个类中有个buildMachine方法如下,增加一段"else if"代码后解决问题:
private StateMachine<S, E> buildMachine(Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
Map<S, StateHolder<S, E>> holderMap, Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState,
TransitionsData<S, E> stateMachineTransitions, TaskExecutor taskExecutor, TaskScheduler taskScheduler, String machineId,
UUID uuid, StateMachineModel<S, E> stateMachineModel)
...
// 如果当前状态是虚拟状态,而且类型是JOIN
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) {
S s = stateData.getState();
List<S> list = stateMachineTransitions.getJoins().get(s);
List<State<S, E>> joins = new ArrayList<State<S,E>>();
// if join source is a regionstate, get
// it's end states from regions
if (list.size() == 1) {
State<S, E> ss1 = stateMap.get(list.get(0));
// 判断状态是RegionState时,才会把子状态机的终结状态加入到joins中
if (ss1 instanceof RegionState) {
Collection<Region<S, E>> regions = ((RegionState<S, E>)ss1).getRegions();
for (Region<S, E> r : regions) {
Collection<State<S, E>> ss2 = r.getStates();
for (State<S, E> ss3 : ss2) {
if (ss3.getPseudoState() != null && ss3.getPseudoState().getKind() == PseudoStateKind.END) {
joins.add(ss3);
continue;
}
}
}
// 下面这个else if是我加的代码
// 判断如果当前状态是子状态机状态类型
// 也把子状态的终结状态加入到joins中
} else if (ss1 instanceof StateMachineState) {
Collection<State<S, E>> subStates = ((StateMachineState) ss1).getSubmachine().getStates();
for (State<S, E> subState : subStates) {
if (subState.getPseudoState() != null && subState.getPseudoState().getKind() == PseudoStateKind.END) {
joins.add(subState);
}
}
}
} else {
for (S fs : list) {
joins.add(stateMap.get(fs));
}
}
List<JoinStateData<S, E>> joinTargets = new ArrayList<JoinStateData<S, E>>();
Collection<TransitionData<S, E>> transitions = stateMachineTransitions.getTransitions();
for (TransitionData<S, E> tt : transitions) {
if (tt.getSource() == s) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(tt.getTarget()));
if (holder.getState() == null) {
holderMap.put(tt.getTarget(), holder);
}
joinTargets.add(new JoinStateData<S, E>(holder, tt.getGuard()));
}
}
JoinPseudoState<S, E> pseudoState = new JoinPseudoState<S, E>(joins, joinTargets);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
}
...
}
这样就能解决Fork+Join+单个Region无法join的问题了
Github issue: https://github.com/spring-projects/spring-statemachine/issues/337