squirrel(松鼠)状态机的介绍及使用

article/2025/9/11 2:08:25

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");}
}

定义上下文对象

// 同声明式状态机

定义状态枚举

// 同声明式状态机

定义事件枚举

// 同声明式状态机

http://chatgpt.dhexx.cn/article/DqiayGr3.shtml

相关文章

将图片转换为base64编码

1、base64编码简介 Base64是一种可逆的编码方式&#xff0c;简单来讲就是一种将64个Ascii字符来表示成二进制数据的方法。主要用于将不可打印的字符转换成可打印字符&#xff0c;或者简单的说将二进制数据编码成Ascii字符。Base64是网络上最常用的传输8bit字节数据的编码方式之…

前端理解base64

一、背景&#xff1a;ascii码 字符>二进制 计算机中所有数据的存储都是以二进制模式&#xff0c;比如想要存储abcd需将其转化为二进制&#xff0c;具体用哪些二进制来表示哪个符号*&#xff0c;有一个统一的编码规则&#xff0c;这就是ascii。 ASCII 码使用指定的7 位或8…

Web 图片Base64编码

转载自: 【前端攻略】&#xff1a;玩转图片Base64编码 引言   图片处理在前端工作中可谓占据了很重要的一壁江山。而图片的 base64 编码可能相对一些人而言比较陌生&#xff0c;本文不是从纯技术的角度去讨论图片的 base64 编码。标题略大&#xff0c;不过只是希望通过一些…

base64编码解码器【C++】

在线编码解码工具https://base64.us/所有结果可以使用上述网站检验。 什么是base64编码&#xff1f; base64编码是一种编码方式 用64 1 个字符表示字符 本质是将三位8比特字符扩增为四位8比特字符&#xff0c;但是这么说开始可能很闷逼。 给个图&#xff0c;这个编码是以3字…

前端图片在线转换Base64 图片编码Base64

首先讲一下什么是图片Base64编码&#xff1a; 简单来说&#xff0c;图片的base64编码就是可以将一副图片数据编码成一串字符串&#xff0c;使用该字符串代替图像地址。 为什么要用Base64&#xff1f; 我们知道&#xff0c;我们所看到的网页上的每一个图片&#xff0c;都是需…

理解图片base64编码

什么是编码解码 编码&#xff1a;利用特定的算法&#xff0c;对原始内容进行处理&#xff0c;生成运算后的内容&#xff0c;形成另一种数据的表现形式&#xff0c;可以根据算法&#xff0c;再还原回来&#xff0c;这种操作称之为编码。 解码&#xff1a;利用编码使用的算法的…

【HTML5】------- 图片Base64编码详解

引言   图片处理在前端工作中可谓占据了很重要的一壁江山。而图片的 base64 编码可能相对一些人而言比较陌生&#xff0c;本文不是从纯技术的角度去讨论图片的 base64 编码。标题略大&#xff0c;不过只是希望通过一些浅显的论述&#xff0c;让你知道什么是图片的 base64 编码…

记录:Base64编码步骤详解

文章目录 具体转换步骤实例演示&#xff1a;AbC实例演示二&#xff1a;字节数不足三个例&#xff1a;A例&#xff1a;8H ASCII编码表Base64编码表 具体转换步骤 第一步&#xff0c;将待转换的字符串每三个字节分为一组&#xff0c;每个字节占8bit&#xff0c;那么共有24个二进…

对图片进行base64 编码

原文地址&#xff1a;https://www.cnblogs.com/coco1s/p/4375774.html 引言 图片处理在前端工作中可谓占据了很重要的一壁江山。而图片的 base64 编码可能相对一些人而言比较陌生&#xff0c;本文不是从纯技术的角度去讨论图片的 base64 编码。标题略大&#xff0c;不过只是希…

php将图片转成base64编码,利用PHP将图片转换成base64编码的实现方法

利用PHP将图片转换成base64编码的实现方法 先来说一下为什么我们要对图片base64编码 base64是当前网络上最为常见的传输8Bit字节代码的编码方式其中之一。base64主要不是加密,它主要的用途是把某些二进制数转成普通字符用于网络传输。由于这些二进制字符在传输协议中属于控制字…

Hex编码与Base64编码

Hex编码与Base64编码 什么是明文什么是编码Hex编码Hex编码实现细节自定义Hex编码 Base64编码Base64编码实现细节自定义Base64编码 什么是明文 介绍什么是编码之前&#xff0c;首先了解下什么是明文&#xff0c;表示其本身意思的&#xff0c;他就是明文 比如你看到"E6988…

Base64编码linux服务器与网页编码不一致问题

一、起因 在一次环境变量注入的时候&#xff0c;使用Base64将密码编码传入&#xff0c;但是传入后无法登录&#xff0c;但是登录到容器内&#xff0c;使用明文却能登录 二、排错过程 出现这种问题&#xff0c;首先判断密文是否有误&#xff0c;但是在linux服务器内反复base64&…

mysql 图片base64_关于图片的Base64编码

什么是Base64编码 Base64编码是一种图片处理格式,通过特定的算法将图片编码成一长串字符串,在页面上显示的时候,可以用该字符串来代替图片的url属性。 base64编码就是长得像下面这样子的代码: thunder://QUFodHRwOi8vd3d3LmJhaWR1LmNvbS9pbWcvc3NsbTFfbG9nby5naWZaWg== 上面…

前端Base64编码

Base64编码 什么是Base64编码 我们所看到的网页上的每一个图片&#xff0c;都是需要消耗一个 http 请求下载而来的&#xff0c;不管如何&#xff0c;图片的下载始终都要向服务器发出请求&#xff0c;要是图片的下载不用向服务器发出请求&#xff0c;而可以随着 HTML 的下载同…

图片Base64编码

图片处理在前端工作中可谓占据了很重要的一壁江山。而图片的 base64 编码可能相对一些人而言比较陌生&#xff0c;本文不是从纯技术的角度去讨论图片的 base64 编码。标题略大&#xff0c;不过只是希望通过一些浅显的论述&#xff0c;让你知道什么是图片的 base64 编码&#xf…

什么是Base64 编码,Base64 编码有哪些优缺点?

很多朋友在工作中&#xff0c;可能经常会用到Base64编码。Base64编码是网络上很常见的用于8Bit字节码的编码方式之一&#xff0c;那么&#xff0c;大家知道为什么要使用Base64 编码&#xff0c;Base64 编码有哪些优缺点呢&#xff1f;下面&#xff0c;我们一起来研究一下。 什…

前端Base64编码知识,一文打尽

原文: https://juejin.cn/post/6989391487200919566 作者: 云的世界 掘金专栏: 前端基础进阶 大厂技术 高级前端 Node进阶 点击上方 程序员成长指北&#xff0c;关注公众号 回复1&#xff0c;加入高级Node交流群 前言 本文将详细的介绍前端 Base64 编码知识&#xff0c;探索…

Python模板库Mako的语法

原文链接&#xff1a;http://www.yeolar.com/note/2012/08/28/mako-syntax/ Mako模板从一个包含各种类型的内容的文本流解析得到&#xff0c;包括XML、HTML、email文本等。模板还可以包含Mako指令&#xff0c;用来表示变量和表达式替换、控制结构、服务器端注释、整块Python代码…

Django mako 的使用(七)

Django mako 的使用 6.6 mako 的使用 6.6 mako 的使用 安装 mako pip install mako 创建子应用 makos python .\manage.py startapp makos 创建 base_render.py 文件 from django.http import HttpResponse from django.template import RequestContext from django.templat…

mako模版库实践

介绍 这几天在编写一个数据驱动测试框架&#xff0c;其中一个核心模块是根据数据输入&#xff0c;自动转换为测试用例代码。测试数据格式固定&#xff0c;对应的用例代码结构也相对稳定。基于此场景&#xff0c;联想到web页面根据数据从template渲染页面&#xff0c;决定使用类…