squirrel(松鼠)状态机
依赖
<dependency><groupId>org.squirrelframework</groupId><artifactId>squirrel-foundation</artifactId><version>0.3.8</version>
</dependency>
状态机描述
参考:https://segmentfault.com/a/1190000009906469
有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型
状态机可以描述核心业务规则,核心业务内容,总结一下就是a实体,在b状态下,由c角色,在满足d条件时,执行e操作成功后,迁移到f状态下,并产生g事件,通常用于业务复杂的订单等业务里。
生命周期
状态机创建过程
- 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
状态持久化
从StateMachine的事件响应流程中可以看到,TransitionBegin–(exit->transition->entry)–>TransitionComplete–>TransitionEnd,在TransitionComplete发生一个状态已从source迁移到了target状态,所以我选择了在这个时间点进行了状态的持久化(没有选择TransitionEnd做持久化,因为某些场景在持久化完成后还会存在一些外部动作的触发,例如通知第三方系统当前状态已完成变更)。代码详见声明式状态机TransitionComplete()方法。
分布式锁+事务
由于StateMachine实例不是由Spring容器创建,所以这个过程中无法通过注解方式开启事务(Spring没有机会去创建事务代理),我采用了编程式事务,在AbstractStateMachineEngine的fire函数中隐式的实现。代码详见声明式状态机AbstractStateMachineEngine#fire
声明式无类型状态机(实例)
参考:https://blog.csdn.net/a1047003619/article/details/102655930
https://blog.csdn.net/qq_41401062/article/details/104359468
https://www.cnblogs.com/wuqinglong/p/12123121.html
利用状态机模拟一个订单的支付过程
状态机引擎
/*** 创建无类型化状态机,简化状态机,防止过多泛化导致代码不易阅读* 通过Spring创建StateMachineBuilder实例,通过buidler创建状态机(单例)* 业务函数中通过StateMachineBuilder实例创建StateMachine实例,并向StateMachine暴露SpringApplicationContext,以便于StateMachine通过ApplicationContext获取数据层的对象
*/
@Slf4j
public class AbstractStateMachineEngine <T extends UntypedStateMachine> implements ApplicationContextAware {private ApplicationContext applicationContext;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实例时注入@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}/*** 可以通过向OrderContext 上下文传递一些业务参数,比如orderId等等*/public boolean fire(EOrderEvents event, OrderContext context) {T stateMachine = stateMachineBuilder.newUntypedStateMachine(context.geteOrder().getOrderStatus(),applicationContext);// 添加监听器
// stateMachine.addStateMachineListener(new StateMachineListener<UntypedStateMachine, Object, Object, Object>() {
// @Override
// public void stateMachineEvent(StateMachineEvent<UntypedStateMachine, Object, Object, Object> event) {
// log.info("lastState: " + event.getStateMachine().getLastState());
// }
// });
// stateMachine.addDeclarativeListener(new DeclarativeEventListener());// 源码中的日志 demo
// StateMachineLogger logger = new StateMachineLogger(stateMachine);
// logger.startLogging();//由于StateMachine实例不是由Spring容器创建,所以这个过程中无法通过注解方式开启事务(Spring没有机会去创建事务代理),因此采用了编程式事务DataSourceTransactionManager transactionManager = (DataSourceTransactionManager)applicationContext.getBean("transactionManager");DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = transactionManager.getTransaction(def);try {stateMachine.fire(event, context);transactionManager.commit(status);//这里会返回状态机是否出错,如果出错可用于通知Controller层return stateMachine.isError();} catch (Exception ex) {//用于事务回滚transactionManager.rollback(status);return true;}}
}
继承AbstractStateMachineEngine,并加入@Service注解,便可以在项目中注入。(单例)
@Service
public class OrderStateMachineEngine extends AbstractStateMachineEngine<SubmitOrderStateMachine>{}
定义状态机(基于注解的方式)
import lombok.extern.slf4j.Slf4j;
import org.squirrelframework.foundation.fsm.*;
import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters;
import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine;/*** 定义 触发事件、状态变化时,调用的方法* @States 定义状态列表,里面可以包含多个状态* @State定义每个状态,name状态名称,entryStateInit进入状态时调用的方法,exitCallMethod 离开状态是调用的方法,initialState 为true时,为默认状态。* */
@States({@State(name = "INIT", entryCallMethod = "entryStateInit", exitCallMethod = "exitStateInit", initialState = true),@State(name = "WAIT_PAY", entryCallMethod = "entryStateWaitPay", exitCallMethod = "exitStateWaitPay"),@State(name = "WAIT_SEND", entryCallMethod = "entryStateWaitSend", exitCallMethod = "exitStateWaitSend"),@State(name = "PART_SEND", entryCallMethod = "entryStatePartSend", exitCallMethod = "exitStatePartSend"),@State(name = "WAIT_RECEIVE", entryCallMethod = "entryStateWaitReceive", exitCallMethod = "exitStateWaitReceive"),@State(name = "COMPLETE", entryCallMethod = "entryStateComplete", exitCallMethod = "exitStateComplete")
})
@Transitions({@Transit(from = "INIT", to = "WAIT_PAY", on = "SUBMIT_ORDER", callMethod = "submitOrder"),@Transit(from = "WAIT_PAY", to = "WAIT_SEND", on = "PAY", callMethod = "pay"),@Transit(from = "WAIT_SEND", to = "PART_SEND", on = "PART_SEND", callMethod = "partSend"),@Transit(from = "PART_SEND", to = "WAIT_RECEIVE", on = "SEND", callMethod = "send"),@Transit(from = "WAIT_RECEIVE", to = "COMPLETE", on = "COMPLETE", callMethod = "complete")
})
// @StateMachineParameters用来声明状态机泛型参数类型,向AbstractStateMachine传递参数
@StateMachineParameters(stateType = OrderState.class, eventType = OrderEvent.class, contextType = OrderContext.class)
@Slf4j
public class SubmitOrderStateMachine extends AbstractStateMachine<UntypedStateMachine, Object, Object, Object> implements UntypedStateMachine {private OrderService orderService;protected ApplicationContext applicationContext;//定义构造函数接受ApplicationContext注入//([参看New State Machine Instance](http://hekailiang.github.io/squirrel/))public SubmitOrderStateMachine(ApplicationContext applicationContext) {this.applicationContext = applicationContext;// 通过applicationContext注入orderServicethis.orderService = applicationContext.getBean(OrderService.class);}// 状态转换时调用的方法,需要将方法名配置在 callMethod 内// 若【方法名】符合 transitFrom[fromStateName]To[toStateName] 格式,不需要配置 callMethodpublic void submitOrder(OrderState fromState, OrderState toState, OrderEvent Event, OrderContext Context) {log.info("转换事件 {}=>{} on {} with {}.", fromState, toState, event, context);orderService.submitOrder(toState);}...public void complete(OrderState fromState, OrderState toState, OrderEvent Event, OrderContext Context) {log.info("转换事件 {}=>{} on {} with {}.", fromState, toState, event, context);System.out.println("complete");}// 符合 entry[StateName] 格式,不需要配置 callMethodpublic void entryStateInit(OrderState fromState, OrderState toState, OrderEvent Event, OrderContext Context) {log.info("进入状态 {}=>{} on {} with {}.", from, to, event, context);System.out.println("entryStateInit");}// 符合 exit[StateName] 格式,不需要配置 callMethodpublic void exitStateInit(OrderState fromState, OrderState toState, OrderEvent Event, OrderContext Context) {log.info("退出状态 {}=>{} on {} with {}.", from, to, event, context);System.out.println("exitStateInit");}...// ==========================================================================================// 如果不想用 DeclarativeEventListener 这种声明在单独类里的方法,可以直接重写以下方法,效果是一样的// 两者同时用也可以,为了代码方便最好别这样// ==========================================================================================@Overrideprotected void afterTransitionCausedException(Object fromState, Object toState, Object event, Object context) {/*** 当状态转换过程中出现异常,已执行的action列表将失效并且状态机会进入error状态,意思就是状态机实例不会再处理任何event。* 假如用户继续向状态机发送event,便会抛出IllegalStateException异常。所有状态转换过程中发生的异常,包括action执行和外部listener调用,会被包装成TransitionException(未检查异常)。* 目前,默认的异常处理策略非常简单并且粗暴的连续抛出异常,可以参阅AbstractStateMachine.afterTransitionCausedException方法。*/log.info("Override 发生错误 {}", getLastException().getMessage());Throwable targeException = getLastException().getTargetException();// recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'if(targeException instanceof IllegalArgumentException &&fromState.equals("A") && toState.equals("B") && event.equals("ToB")) {// do some error clean up job here// ...// after recovered from this exception, reset the state machine status back to normalsetStatus(StateMachineStatus.IDLE);} else if(...) {// recover from other exception ...} else {// afterTransitionCausedException 默认的实现是直接抛异常super.afterTransitionCausedException(fromState, toState, event, context);}}@Overrideprotected void beforeTransitionBegin(Object fromState, Object event, Object context) {// 转换开始时被调用System.out.println();
// super.beforeTransitionBegin(fromState, event, context);log.info("Override beforeTransitionBegin");}@Overrideprotected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {// 转换完成时被调用
// super.afterTransitionCompleted(fromState, toState, event, context);log.info("Override afterTransitionCompleted");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());}}@Overrideprotected void afterTransitionEnd(Object fromState, Object toState, Object event, Object context) {// 转换结束时被调用// super.afterTransitionEnd(fromState, toState, event, context);log.info("Override afterTransitionEnd");}@Overrideprotected void afterTransitionDeclined(Object fromState, Object event, Object context) {// 当转换被拒绝时被调用。实际是调用 callMethod 中的方法被调用时,抛出异常时被调用
// super.afterTransitionDeclined(fromState, event, context);log.info("Override afterTransitionDeclined");}@Overrideprotected void beforeActionInvoked(Object fromState, Object toState, Object event, Object context) {// 当转换开始时被调用。实际是 callMethod 中的方法被调用时,先调用该方法。类似于 AOP 的效果// super.beforeActionInvoked(fromState, toState, event, context);log.info("Override beforeActionInvoked");}@Overrideprotected void afterActionInvoked(Object fromState, Object toState, Object event, Object context) {// 当转换结束时被调用。实际是 callMethod 被调用后,调用该方法。类似于 AOP 的效果// super.afterActionInvoked(fromState, toState, event, context);log.info("Override afterActionInvoked");}
}
定义状态枚举
@Get
public enum OrderState {INIT(1,"开始"),WAIT_PAY(2,"待支付"),WAIT_SEND(3,"待发送"),PART_SEND(4,"配送"),WAIT_RECEIVE(5,"待接收"),COMPLETE(6,"完成"),CANCELED(7,"取消");private String desc;private int code;public static OrderState getState(String state) {for (OrderState orderState : OrderState.values()) {if (orderState.name().equalsIgnoreCase(state)) {return orderState;}}return null;}public static OrderStates getState(int code) {for (OrderStates orderState : OrderStates.values()) {if (orderState.ordinal()+1 == code) {return orderState;}}return null;}
}
定义事件枚举
public enum OrderEvent {SUBMIT_ORDER, //提交订单;INIT ==> WAIT_PAYPAY, //支付完成;WAIT_PAY ==> WAIT_SENDPART_SEND, //等待配送;WAIT_SEND ==> PART_SENDSEND, //配送中;PART_SEND ==> WAIT_RECEIVECOMPLETE //完成;WAIT_RECEIVE ==> COMPLETE
}
定义上下文
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderContext {public OrderDTO orderDTO;
}
业务相关代码
Service的实现,更新订单状态
@Service
public class OrderService {@AutowiredOrderDTOMapper orderDTOMapper;public int submitOrder(OrderState state) {OrderDTO orderDTO = new OrderDTO();orderDTO.setState(state);orderDTOMapper.insert(orderDTO);return 1;}
}
mapper
@Mapper
public interface OrderDTOMapper {int insert(OrderDTO orderDTO);}
订单的实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {private Integer id;private OrderState state;private Date createTime;private Date updateTime;
}
定义监听器
可以不用 DeclarativeEventListener 这种声明在单独类里的形式
import lombok.extern.slf4j.Slf4j;
import org.squirrelframework.foundation.exception.TransitionException;
import org.squirrelframework.foundation.fsm.Action;
import org.squirrelframework.foundation.fsm.annotation.*;/*** 声明式监听*/
@Slf4j
public class DeclarativeEventListener {/*** 转换事件开始时进行调用*/@OnTransitionBeginpublic void transitionBegin(FSMEvent event) {System.out.println();log.info("transitionBegin event {}", event);}/*** 转换事件开始时进行调用* 可以加入条件* 'event'(E), 'from'(S), 'to'(S), 'context'(C) and 'stateMachine'(T) can be used in MVEL scripts*/@OnTransitionBegin(when="event.name().equals(\"ToB\")")public void transitionBeginConditional() {log.info("transitionBeginConditional");}/*** 转换事件结束时进行调用* 这个方法必须是 public 并且返回值是 void*/@OnTransitionEnd@ListenerOrder(10)public void transitionEnd() {log.info("transitionEnd");System.out.println();}@OnTransitionCompletepublic void transitionComplete(String from, String to, FSMEvent event, Integer context) {log.info("transitionComplete {}=>{} on {} with {}", from, to, event, context);}@OnTransitionExceptionpublic void transitionException(String from, String to, FSMEvent event, Integer context) {log.info("transitionException");}/*** 当转换被拒绝时,将调用注有TransitionDecline的方法*/@OnTransitionDeclinepublic void transitionDeclined(String from, FSMEvent event, Integer context) {log.info("transitionDeclined {}=>??? on {} with {}", from, event, context);}/*** 带有 OnAfterActionExecuted 注释的方法将在调用操作之前被调用* 实际是 callMethod 中的方法被调用钱执行这个方法。类似于 AOP 的效果,运行一下即可知道*/@OnBeforeActionExecutedpublic void onBeforeActionExecuted(Object sourceState, Object targetState,Object event, Object context, int[] mOfN, Action<?, ?, ?,?> action) {log.info("onBeforeActionExecuted");}/*** 带有 OnAfterActionExecuted 注释的方法将在调用操作之后被调用* 实际是 callMethod 中的方法被调用后执行这个方法。类似于 AOP 的效果,运行一下即可知道*/@OnAfterActionExecutedpublic void onAfterActionExecuted(Object sourceState, Object targetState,Object event, Object context, int[] mOfN, Action<?, ?, ?,?> action) {log.info("onAfterActionExecuted");}/*** 带有 OnActionExecException 注释的方法将在调用操作异常之后被调用* 实际是 callMethod 中的方法被调用时抛异常了之后执行这个方法。类似于 AOP 的效果,运行一下即可知道*/@OnActionExecExceptionpublic void onActionExecException(Action<?, ?, ?,?> action, TransitionException e) {log.info("onActionExecException");}
}
Controller类测试
注入OrderStateMachineEngine ,初始化状态为INIT,通过OrderStateMachineEngine 触发SUBMIT_ORDER事件,发生状态变化,并将转换后的状态插入数据库
@RestController
@RequestMapping(value = "/order/")
public class OrderController {// 注入状态机@AutowiredOrderStateMachineEngine orderStateMachineEngine;@RequestMapping("/test")public void test(){OrderDTO orderDTO = new OrderDTO(OrderState.INIT);OrderContext orderContext = new OrderContext(orderDTO);// 启动状态机,触发某事件,发生状态变化orderStateMachineEngine.fire(OrderEvent.SUBMIT_ORDER,orderContext);}
}
流式API式状态机(示例)
参考:https://www.yangguo.info/2015/02/01/squirrel/
状态机引擎
StateMachine接口需要以下4种泛型参数。
- T代表实现的状态机类型。
- S代表实现的状态类型。
- E代表实现的事件类型。
- C代表实现的外部上下文类型。
/*** 创建状态机* 通过Spring创建StateMachineBuilder实例,通过buidler创建状态机(单例)* 业务函数中通过StateMachineBuilder实例创建StateMachine实例,并向StateMachine暴露SpringApplicationContext,以便于StateMachine通过ApplicationContext获取数据层的对象
*/
@Slf4j
@Componet
public class StateMachineEngine implements ApplicationContextAware {private ApplicationContext applicationContext;protected StateMachineBuilder<MyStateMachine, MyState, MyEvent, MyContext> builder;@SuppressWarnings("unchecked")public StateMachineEngine() {builder = StateMachineBuilderFactory.create(MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);// 状态机builder创建之后,可以使用流失API来定义状态机的state/transition/action// 创建一个状态从状态A到B,并且MyEvent.GoToB事件触发,调用MyCallMethod动作方法的externalTransitionbuilder.externalTransition().from(MyState.A).to(MyState.B).on(MyEvent.GoToB).callMethod = "MyCallMethod";}//注入applicationContext,并在创建StateMachine实例时注入@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}/*** 可以通过向OrderContext 上下文传递一些业务参数,比如orderId等等*/public boolean fire(EOrderEvents event, OrderContext context) {MyStateMachine stateMachine = stateMachineBuilder.newStateMachine(context.geteOrder().getOrderStatus(),applicationContext);//由于StateMachine实例不是由Spring容器创建,所以这个过程中无法通过注解方式开启事务(Spring没有机会去创建事务代理),因此采用了编程式事务DataSourceTransactionManager transactionManager = (DataSourceTransactionManager)applicationContext.getBean("transactionManager");DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = transactionManager.getTransaction(def);try {stateMachine.fire(event, context);transactionManager.commit(status);//这里会返回状态机是否出错,如果出错可用于通知Controller层return stateMachine.isError();} catch (Exception ex) {//用于事务回滚transactionManager.rollback(status);return true;}}
}
定义状态机
import lombok.extern.slf4j.Slf4j;
import org.squirrelframework.foundation.fsm.*;
import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters;
import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine;/*** 定义 触发事件、状态变化时,调用的方法
*/
@Slf4j
public class MyStateMachine extends AbstractStateMachine<MyStateMachine, MyState, MyEvent, MyContext> {private MyService myService;protected ApplicationContext applicationContext;//定义构造函数接受ApplicationContext注入public SubmitOrderStateMachine(ApplicationContext applicationContext) {this.applicationContext = applicationContext;// 通过applicationContext注入orderServicethis.myService = applicationContext.getBean(MyService.class);}// 状态转换时调用的方法,需要将方法名配置在 callMethod 内// 若【方法名】符合 transitFrom[fromStateName]To[toStateName] 格式,不需要配置 callMethodpublic void MyCallMethod(MyState fromState, OrderState MyState,MyEvent Event, MyContext Context) {log.info("转换事件 {}=>{} on {} with {}.", fromState, toState, event, context);myService.myMethod(toState);}...// ==========================================================================================// 如果不想用 DeclarativeEventListener 这种声明在单独类里的方法,可以直接重写以下方法,效果是一样的// 两者同时用也可以,为了代码方便最好别这样// ==========================================================================================@Overrideprotected void afterTransitionCausedException(Object fromState, Object toState, Object event, Object context) {/*** 当状态转换过程中出现异常,已执行的action列表将失效并且状态机会进入error状态,意思就是状态机实例不会再处理任何event。* 假如用户继续向状态机发送event,便会抛出IllegalStateException异常。所有状态转换过程中发生的异常,包括action执行和外部listener调用,会被包装成TransitionException(未检查异常)。* 目前,默认的异常处理策略非常简单并且粗暴的连续抛出异常,可以参阅AbstractStateMachine.afterTransitionCausedException方法。*/log.info("Override 发生错误 {}", getLastException().getMessage());Throwable targeException = getLastException().getTargetException();// recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'if(targeException instanceof IllegalArgumentException &&fromState.equals("A") && toState.equals("B") && event.equals("ToB")) {// 在这里做一些错误清理工作// ...// 恢复此异常后,将状态机状态恢复为正常状态setStatus(StateMachineStatus.IDLE);} else if(...) {// 从其他异常中恢复...} else {// afterTransitionCausedException 默认的实现是直接抛异常super.afterTransitionCausedException(fromState, toState, event, context);}}@Overrideprotected void beforeTransitionBegin(Object fromState, Object event, Object context) {// 转换开始时被调用System.out.println();
// super.beforeTransitionBegin(fromState, event, context);log.info("Override beforeTransitionBegin");}@Overrideprotected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {// 转换完成时被调用
// super.afterTransitionCompleted(fromState, toState, event, context);log.info("Override afterTransitionCompleted");}@Overrideprotected void afterTransitionEnd(Object fromState, Object toState, Object event, Object context) {// 转换结束时被调用// super.afterTransitionEnd(fromState, toState, event, context);log.info("Override afterTransitionEnd");}@Overrideprotected void afterTransitionDeclined(Object fromState, Object event, Object context) {// 当转换被拒绝时被调用。实际是调用 callMethod 中的方法被调用时,抛出异常时被调用
// super.afterTransitionDeclined(fromState, event, context);log.info("Override afterTransitionDeclined");}@Overrideprotected void beforeActionInvoked(Object fromState, Object toState, Object event, Object context) {// 当转换开始时被调用。实际是 callMethod 中的方法被调用时,先调用该方法。类似于 AOP 的效果// super.beforeActionInvoked(fromState, toState, event, context);log.info("Override beforeActionInvoked");}@Overrideprotected void afterActionInvoked(Object fromState, Object toState, Object event, Object context) {// 当转换结束时被调用。实际是 callMethod 被调用后,调用该方法。类似于 AOP 的效果// super.afterActionInvoked(fromState, toState, event, context);log.info("Override afterActionInvoked");}
}
定义上下文对象
// 同声明式状态机
定义状态枚举
// 同声明式状态机
定义事件枚举
// 同声明式状态机