squirrel-foundation狀態機的使用細節
squirrel-foundation狀態機的使用細節
上一篇文章介紹了stateless4j、spring-statemachine以及squirrel-foundation三款狀態機引擎的實現原理,以及我為何選擇squirrel-foundation作為解決方案。本文主要介紹一下項目中如何使用squirrel-foundation的一些細節以及如何與spring進行集成。在閱讀本文前,建議先閱讀官方的使用手冊。
生命周期
狀態機創建過程
- StateMachine: StateMachine實例由StateMachineBuilder創建不被共享,對於使用annotation方式(或fluent api)定義的StateMachine,StateMachine實例即根據此定義創建,相應的action也由本實例執行,與spring的集成最終要的就是講spring的bean實例注入給由builder創建的狀態機實例;
- StateMachineBuilder: 本質上是由StateMachineBuilderFactory創建的動態代理。被代理的StateMachineBuilder默認實現為StateMachineBuilderImpl,內部描述了狀態機實例創建細節包括State、Event、Context類型信息、constructor等,同時也包含了StateMachine的一些全局共享資源包括StateConverter、EventConverter、MvelScriptManager等。StateMachineBuilder可被複用,使用中可被實現為singleton;
- StateMachineBuilderFactory: 為StateMachineBuilder創建的動態代理實例;
事件處理過程
- 狀態正常遷移 TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEnd
- 狀態遷移異常 TransitionBegin--(exit->transition->entry)-->TransitionException-->TransitionEnd
- 狀態遷移事件拒絕 TransitionBegin-->TransitionDeclined-->TransitionEnd
spring集成
從statemachine的生命流程上可以看到,StateMachineBuilder可以單例方式由spring container管理,而stateMachine的instance的生命周期伴隨著請求(或業務)。
從這兩點出發,集成spring需要完成兩件事:
- (1).通過Spring創建StateMachineBuilder實例;
- (2).業務函數中通過(1)的StateMachineBuilder實例創建StateMachine實例,並向StateMachine暴露SpringApplicationContext;
泛型參數+覆蓋默認構造函數隱藏StateMachineBuilder創建細節,實現ApplicationContextAware接口,接受applicationContext注入,並注入給stateMachine實例。
public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
protected UntypedStateMachineBuilder stateMachineBuilder = null;
@SuppressWarnings("unchecked")
public AbstractStateMachineEngine() {
//識別泛型參數
Class<T> genericType = (Class<T>)GenericTypeResolver.resolveTypeArgument(getClass(),
AbstractStateMachineEngine.class);
stateMachineBuilder = StateMachineBuilderFactory.create(genericType, ApplicationContext.class);
}
//注入applicationContext,並在創建StateMachine實例時注入
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//delegate fire
public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
T stateMachine = stateMachineBuilder.newUntypedStateMachine(
initialState
//暫時開啟debug進行日誌trace
StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
//注入applicationContext
applicationContext);
stateMachine.fire(trigger, context);
}
...
}
@Service
class DiscountRefundStateMachineEngine extends AbstractStateMachineEngine<DiscountRefundStateMachine> {
}
@Service
public class ReturnGoodsStateMachineEngine extends AbstractStateMachineEngine<ReturnGoodsStateMachine> {
}
StateMachine定義,接受SpringContext注入
@StateMachineParameters(stateType = State.class, eventType = Trigger.class,
//StateMachineContext 自定義上下文,用來傳遞數據
contextType = StateMachineContext.class)
@States({
@State(name = "PENDING", initialState = true),
@State(name = "CONFIRMING"),
@State(name = "REJECTED"),
@State(name = "REFUND_APPROVING"),
@State(name = "REFUND_APPROVED"),
@State(name = "REFUND_FINISHED")
})
@Transitions({
@Transit(from = "PENDING", to = "CONFIRMING", on = "APPLY_CONFIRM",
callMethod = "doSomething"),
@Transit(from = "CONFIRMING", to = "REJECTED", on = "REJECT"),
@Transit(from = "CONFIRMING", to = "REFUND_APPROVING", on = "APPLY_APPROVED"),
@Transit(from = "REFUND_APPROVING", to = "REFUND_APPROVED", on = "REFUND_APPROVED"),
@Transit(from = "REFUND_APPROVED", to = "REFUND_FINISHED", on = "REFUND_FINISH_CONFIRM")
})
public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
protected ApplicationContext applicationContext;
//定義構造函數接受ApplicationContext注入([參看New State Machine Instance](https://hekailiang.github.io/squirrel/))
public DiscountRefundStateMachine(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void doSomething(State fromState, State toState, Trigger event,
StateMachineContext stateMachineContext) {
DemoBean demoBean = this.applicationContext.get("demoBean");
//do something
}
...
}
狀態持久化
從StateMachine的事件響應流程中可以看到,TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEnd,在TransitionComplete發生一個狀態已從source遷移到了target狀態,所以我選擇了在這個時間點進行了狀態的持久化(沒有選擇TransitionEnd做持久化,因為某些場景在持久化完成後還會存在一些外部動作的觸發,例如通知第三方係統當前狀態已完成變更)。
public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
..
@Override
protected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {
if (context instanceof StateMachineContext && toState instanceof State) {
StateMachineContext stateMachineContext = (StateMachineContext)context;
//從上下文中獲取需要持久化的數據,例如訂單ID等
Rma rma = stateMachineContext.get(MessageKeyEnum.RMA);
//持久化
rma.setStatus((State)toState);
this.applicationContext.get("rmaRepository").updateRma(rma);
} else {
throw new Exception("type not support, context expect " + StateMachineContext.class.getSimpleName() + ", actually "
+ context.getClass().getSimpleName() + ", state expect " + State.class.getSimpleName()
+ ", actually "
+ toState.getClass().getSimpleName());
}
}
}
分布式鎖+事務
由於StateMachine實例不是由Spring容器創建,所以這個過程中無法通過注解方式開啟事務(Spring沒有機會去創建事務代理),我采用了編程式事務,在AbstractStateMachineEngine的fire函數中隱式的實現。
AbstractStateMachineEngine#fire
public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
...
public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
JedisLock jedisLock = jedisLockFactory.buildLock(rmaId);
//爭用分布式鎖
if (jedisLock.tryLock()) {
try {
T stateMachine = stateMachineBuilder.newUntypedStateMachine(
initialState
//暫時開啟debug進行日誌trace
StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
//注入applicationContext
applicationContext);
DataSourceTransactionManager transactionManager = applicationContext.get("transactionManager")
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
stateMachine.fire(trigger, context)
transactionManager.commit(status);
} catch (Exception ex) {
transactionManager.rollback(status);
throw ex;
}
} finally {
jedisLock.release();
}
}
...
}
}
使用graphviz生成狀態拓撲圖
squirrel statemachine提供了DotVisitor、SCXMLVisitor兩種實現方式用於生成狀態機描述文件,項目裏我選擇了graphviz用來做狀態拓撲
graphviz gui工具下載
PS:由於squirrel默認的DotVisitorImpl對帶中文描述屬性的State/Event枚舉不友好,我在原有代碼上做了一些調整,有類似需求的可以看這裏
更多文章請訪問我的博客
轉載請注明出處
最後更新:2017-08-31 15:02:27