在持久化后,我很难从数据库中获得层次状态机的正确状态。父机有两个状态("IN_ANALYSIS"、"OPEN"(,而IN_ANALYSIS状态有子状态("IN_PROGRESS"、"PENDING_SIGOFF2"、"COMPLETED"(。
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.IN_ANALYSIS)
.state(States.IN_ANALYSIS)
.state(States.OPEN)
.and()
.withStates()
.parent(States.IN_ANALYSIS)
.initial(States.IN_PROGRESS)
.state(States.IN_PROGRESS)
.state(States.PENDING_SIGNOFF)
.state(States.PENDING_SIGNOFF2)
.state(States.COMPLETED,closedEntryAction(), null);
}
每当我将状态机留在一个中间子状态(如(PENDING_SIGNOFF,PENDING_IGNOFF2(上,然后再次从数据库中获取它时,子状态就会重置为初始状态(IN_PROGRESS(。
我使用的是弹簧状态机框架提供的JPA持久性
@Configuration
@Profile("jpa")
public static class JpaPersisterConfig {
@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
并且使用";DefaultStateMachineService";
@Configuration
public static class ServiceConfig {
@Bean
public StateMachineService<States, Events> stateMachineService(
StateMachineFactory<States, Events> stateMachineFactory,
StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}
}
Spring似乎将分层状态保存为不同的状态机上下文,正如它在数据多持久性示例中所描述的那样。尝试通过将嵌套状态标记为区域并设置区域id来配置区域。然后您可以通过id和区域id 请求状态机
StateMachine<States, Events> sm = defaultStateMachineService.acquireStateMachine(stateMachineId + "#" + regionId)
问题出现在DefaultStateMachineService类中,在获取SM期间,服务创建一个新的SM,JpaPersistingStateMachineInterceptor将新的SM保持为初始状态,然后DefaultStateMachine服务从DB读取状态(该状态已被重写(,并用重写的状态调用restoreStateMachine方法。因此,要解决这个问题,您必须创建自己的StateMachineService实现。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.Lifecycle;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachineException;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.service.DefaultStateMachineService;
import org.springframework.statemachine.service.StateMachineService;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
public class PrimeStateMachineService<S, E> implements StateMachineService<S, E>, DisposableBean {
private final static Log log = LogFactory.getLog(DefaultStateMachineService.class);
private final StateMachineFactory<S, E> stateMachineFactory;
private final Map<String, StateMachine<S, E>> machines = new HashMap<String, StateMachine<S, E>>();
private StateMachinePersist<S, E, String> stateMachinePersist;
/**
* Instantiates a new default state machine service.
*
* @param stateMachineFactory the state machine factory
*/
public PrimeStateMachineService(StateMachineFactory<S, E> stateMachineFactory) {
this(stateMachineFactory, null);
}
/**
* Instantiates a new default state machine service.
*
* @param stateMachineFactory the state machine factory
* @param stateMachinePersist the state machine persist
*/
public PrimeStateMachineService(StateMachineFactory<S, E> stateMachineFactory,
StateMachinePersist<S, E, String> stateMachinePersist) {
Assert.notNull(stateMachineFactory, "'stateMachineFactory' must be set");
this.stateMachineFactory = stateMachineFactory;
this.stateMachinePersist = stateMachinePersist;
}
@Override
public final void destroy() throws Exception {
doStop();
}
@Override
public StateMachine<S, E> acquireStateMachine(String machineId) {
return acquireStateMachine(machineId, true);
}
@Override
public StateMachine<S, E> acquireStateMachine(String machineId, boolean start) {
log.info("Acquiring machine with id " + machineId);
StateMachine<S, E> stateMachine;
synchronized (machines) {
stateMachine = machines.get(machineId);
if (isNull(stateMachine)) {
if (nonNull(stateMachinePersist)) {
try {
StateMachineContext<S, E> stateMachineContext = stateMachinePersist.read(machineId);
if (isNull(stateMachineContext)) {
stateMachine = stateMachineFactory.getStateMachine(machineId);
log.info("Getting new machine from factory with id " + machineId);
} else {
stateMachine = restoreStateMachine(stateMachineFactory.getStateMachine(machineId), stateMachineContext);
log.info("State machine restored from repository with id " + machineId);
}
} catch (Exception e) {
log.error("Error handling context", e);
throw new StateMachineException("Unable to read context from store", e);
}
}
machines.put(machineId, stateMachine);
}
}
return handleStart(stateMachine, start);
}
@Override
public void releaseStateMachine(String machineId) {
log.info("Releasing machine with id " + machineId);
synchronized (machines) {
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
stateMachine.stop();
}
}
}
@Override
public void releaseStateMachine(String machineId, boolean stop) {
log.info("Releasing machine with id " + machineId);
synchronized (machines) {
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
handleStop(stateMachine, stop);
}
}
}
/**
* Determines if the given machine identifier denotes a known managed state machine.
*
* @param machineId machine identifier
* @return true if machineId denotes a known managed state machine currently in memory
*/
public boolean hasStateMachine(String machineId) {
synchronized (machines) {
return machines.containsKey(machineId);
}
}
/**
* Sets the state machine persist.
*
* @param stateMachinePersist the state machine persist
*/
public void setStateMachinePersist(StateMachinePersist<S, E, String> stateMachinePersist) {
this.stateMachinePersist = stateMachinePersist;
}
protected void doStop() {
log.info("Entering stop sequence, stopping all managed machines");
synchronized (machines) {
ArrayList<String> machineIds = new ArrayList<>(machines.keySet());
for (String machineId : machineIds) {
releaseStateMachine(machineId, true);
}
}
}
protected StateMachine<S, E> restoreStateMachine(StateMachine<S, E> stateMachine, final StateMachineContext<S, E> stateMachineContext) {
if (stateMachineContext == null) {
return stateMachine;
}
stateMachine.stop();
stateMachine
.getStateMachineAccessor()
.doWithAllRegions(function -> function.resetStateMachine(stateMachineContext));
return stateMachine;
}
protected StateMachine<S, E> handleStart(StateMachine<S, E> stateMachine, boolean start) {
if (start) {
if (!((Lifecycle) stateMachine).isRunning()) {
PrimeStateMachineService.StartListener<S, E> listener = new PrimeStateMachineService.StartListener<>(stateMachine);
stateMachine.addStateListener(listener);
stateMachine.start();
try {
listener.latch.await();
} catch (InterruptedException e) {
}
}
}
return stateMachine;
}
protected StateMachine<S, E> handleStop(StateMachine<S, E> stateMachine, boolean stop) {
if (stop) {
if (((Lifecycle) stateMachine).isRunning()) {
PrimeStateMachineService.StopListener<S, E> listener = new PrimeStateMachineService.StopListener<>(stateMachine);
stateMachine.addStateListener(listener);
stateMachine.stop();
try {
listener.latch.await();
} catch (InterruptedException e) {
}
}
}
return stateMachine;
}
private static class StartListener<S, E> extends StateMachineListenerAdapter<S, E> {
final CountDownLatch latch = new CountDownLatch(1);
final StateMachine<S, E> stateMachine;
public StartListener(StateMachine<S, E> stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
this.stateMachine.removeStateListener(this);
latch.countDown();
}
}
private static class StopListener<S, E> extends StateMachineListenerAdapter<S, E> {
final CountDownLatch latch = new CountDownLatch(1);
final StateMachine<S, E> stateMachine;
public StopListener(StateMachine<S, E> stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public void stateMachineStopped(StateMachine<S, E> stateMachine) {
this.stateMachine.removeStateListener(this);
latch.countDown();
}
}
}
我在功能测试上下文中遇到了同样的问题。采取的方法是通过服务(不释放它(在内存中创建和种子化所需的状态机,并端到端地运行测试。通过这种方式,服务没有提交持久性,这导致状态回滚到初始状态
public static StateMachine restoreMachine(final StateMachine<States, Events> stateMachine,
final States currentState, final ExtendedState extendedState) {
stateMachine.getStateMachineAccessor().doWithAllRegions(t ->
t.resetStateMachine(
new DefaultStateMachineContext<>(currentState, Events.ANY_DESIRED_STATE, null, extendedState, null, "your-machine-id")));
return stateMachine;
}