美文网首页
Mina StateMachine

Mina StateMachine

作者: 鱼蛮子9527 | 来源:发表于2022-05-28 15:30 被阅读0次

StateMachine(状态机)在维护多状态数据时有非常好的作用,现在 GitHub上 Star 排名最前的是squirrel-foundation 以及 spring-statemachine,而 Mina 的 StateMachine 好像并没有对外提供,多用于Mina 的高级特性里面。

三者比较

了解了下 spring-statemachine,提供特别完善的扩展方式,Interceptor,Listener,甚至支持分布式。但是上手使用还是有一定的难度,代码也比较复杂,状态机的实例比较重。没有看到较好的现实应用实例,如对一个任务的管理可能是需要根据 ID 从数据库中获取状态再根据当前状态,事件去决定 transition。看到 Spring 中是使用 PersistStateMachineHandler 来处理类似的情况,需要停止状态机,重置状态机为相应状态再触发。个人感觉不是很优雅,也可能是我看的不够深入没有理解其精髓,如果有别的实现方式欢迎留言告知。

squirrel-foundation 相比较 spring-statemachine,就比较容易上手了,有很完善的帮助示例,应用也比较符合个人对状态机的认知,同时也提供了完善的 Listener 等支持,是比较好的状态机选择。

这里主要介绍 Mina StateMachine,相比前两个可以说寥寥无名,但是在用起来的时候还是很够用的,比较符合现实业务对状态机的要求。核心代码也就几百行,实现逻辑足够清晰,看完源码半个小时就够了。如果感觉不够用,也完全可以根据自己的业务需求进行修改定制。

使用示例

Mina StateMachine 的官方 Guide 上有很详细的使用教程。我以自己业务中的应用来举例下:

实体及对外接口定义
/**
 * 任务实体
 * @author 鱼蛮 on 2019/2/23
 **/
@Getter
@Setter
public class Task {
    /**任务ID*/
    private Integer id;
    /**任务名称*/
    private String name;
    /**任务状态*/
    private String state;
}

/**
 * 服务接口
 * @author 鱼蛮 on 2019/2/23
 **/
public interface TaskWork {
    /**
     * 任务领取
     * @param taskId
     * @param userName
     */
    void take(Integer taskId, String userName);

    /**
     * 任务提交
     * @param taskId
     */
    void submit(Integer taskId);

    /**
     * 任务审核
     * @param taskId
     * @param auditor
     */
    void audit(Integer taskId, String auditor);
}
状态流转 Handler
/**
 * 服务实际处理器  
 * @author 鱼蛮 on 2019/2/23
 **/
@Slf4j
public class TaskHandler {
    @State public static final String CREATED = "Created";
    @State public static final String TOOK = "Took";
    @State public static final String SUBMITTED = "Submitted";
    @State public static final String AUDITED = "Audited";

    @Transition(on = "take", in = CREATED, next = TOOK)
    public void takeTask(StateContext context, String userName) {
        Task task = (Task)context.getAttribute("task");
        log.info("use:{},take task, taskId:{}", userName, task.getId());
    }

    @Transition(on = "submit", in = {TOOK}, next = SUBMITTED)
    public void submitTask(StateContext context) {
        Task task = (Task)context.getAttribute("task");
        log.info("taskId:{}, submitted", task.getId());
    }

    @Transition(on = "audit", in = SUBMITTED, next = AUDITED)
    public void auditTask(StateContext context, String auditor) {
        Task task = (Task)context.getAttribute("task");
        log.info("auditor:{}, audit task {}", auditor, task.getId());
    }
}
状态机的使用

/**
 * 状态机的使用
 * @author 鱼蛮 on 2019/2/23
 **/
public class TaskSmTest {
    public static void main(String[] args) {
        // 新建handler
        TaskHandler taskHandler = new TaskHandler();
        // 构建状态机
        StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TaskHandler.CREATED, taskHandler);
        // 创建对外接口对象
        TaskWork taskWork = new StateMachineProxyBuilder().setStateContextLookup(new StateContextLookup() {
            @Override
            public StateContext lookup(Object[] objects) {
                Integer taskId = (Integer)objects[0];
                // 这里实际应该根据Id去数据库查询实体信息
                Task task = new Task();
                task.setId(taskId);
                StateContext context = new DefaultStateContext();
                if (taskId == 123) {
                    task.setState(TaskHandler.CREATED);
                } else if (taskId == 124) {
                    task.setState(TaskHandler.TOOK);
                } else if (taskId == 125) {
                    task.setState(TaskHandler.SUBMITTED);
                }
                context.setCurrentState(sm.getState(task.getState()));
                context.setAttribute("task", task);
                return context;
            }
        }).create(TaskWork.class, sm);

        // 领取任务
        taskWork.take(123, "Jack");
        // 提交任务
        taskWork.submit(124);
        // 审核任务
        taskWork.audit(125, "Andy");

        // 构建异常情况,任务状态为CREATED不能直接提交
        StateContext context = new DefaultStateContext();
        context.setCurrentState(sm.getState(TaskHandler.CREATED));
        context.setAttribute("task", new Task());
        Event event = new Event("take", context, new Object[]{123, "Jack"});
        sm.handle(event);
     taskWork.submit(123);
    } 
}

直接执行下,运行结果也完全符合预期:

2019-02-23 15:50:54,570  INFO [main] (TaskHandler.java:22) - use:Jack,take task, taskId:123
2019-02-23 15:50:54,574  INFO [main] (TaskHandler.java:28) - taskId:124, submitted
2019-02-23 15:50:54,574  INFO [main] (TaskHandler.java:34) - auditor:Andy, audit task 125
2019-02-23 15:50:54,575  INFO [main] (TaskHandler.java:22) - use:Jack,take task, taskId:null
Exception in thread "main" org.apache.mina.statemachine.event.UnhandledEventException: Unhandled event: Event[id=submit,context=StateContext[currentState=State[id=Created],attributes={task=com.blackbread.statemachine.mina.Task@4f8e5cde}],arguments={123}]
    at org.apache.mina.statemachine.StateMachine.handle(StateMachine.java:275)
    at org.apache.mina.statemachine.StateMachine.processEvents(StateMachine.java:170)
    at org.apache.mina.statemachine.StateMachine.handle(StateMachine.java:158)
    at org.apache.mina.statemachine.StateMachineProxyBuilder$MethodInvocationHandler.invoke(StateMachineProxyBuilder.java:261)
    at com.sun.proxy.$Proxy5.submit(Unknown Source)
    at com.blackbread.statemachine.mina.TaskSmTest.main(TaskSmTest.java:55)

状态机创建及执行流程

先来分析下状态机的创建以及执行流程,首先看这一行是状态机的创建,初始化过程。

StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TaskHandler.CREATED, taskHandler);

StateMachineFactory#getInstance() 方法中传入的 transitionAnnotation 指定的 TransitionAnnotation.class,以此作为查找 Transition 的注解,同时创建 StateMachineFactory 对象。接着就执行 StateMachineFactory#create() 方法,对 StateMachinne 进行创建。

StateMachineFactory#create()

我们一起看下 StateMachineFactory#create() 方法。

public StateMachine create(String start, Object handler, Object... handlers) {

    Map<String, State> states = new HashMap<>();
    List<Object> handlersList = new ArrayList<>(1 + handlers.length);
    handlersList.add(handler);
    // 从handler中获取带State注解的状态集合,
    // 这里必须是String类型的,如果想用其他类型的需要自己修改源码加以支持    
    handlersList.addAll(Arrays.asList(handlers));     
    LinkedList<Field> fields = new LinkedList<>();
    for (Object h : handlersList) {
        fields.addAll(getFields(h instanceof Class ? (Class<?>) h : h.getClass()));
    }
    // 根据field创建State对象
    for (State state : createStates(fields)) {
        states.put(state.getId(), state);
    }

    if (!states.containsKey(start)) {
        throw new StateMachineCreationException("Start state '" + start + "' not found.");
    }
    // 执行transition与State的绑定
    setupTransitions(transitionAnnotation, transitionsAnnotation, entrySelfTransitionsAnnotation,
                     exitSelfTransitionsAnnotation, states, handlersList);

    return new StateMachine(states.values(), start);
}

这段代码中,主要就是通过反射的方式创建了 State。读取传入的 Handler 类中带有 @State 注解的 String 静态常量字段。如果需要用其他的类型,如 Integer 来标识状态,需要自己修改源码来实现,Mina StateMachine 的扩展性的确不强。

setupTransitions()

其实最核心的 StateMachine 创建代码是 setupTransitions() 方法,我们一起来看下:

private static void setupTransitions(Class<? extends Annotation> transitionAnnotation,
                                     Class<? extends Annotation> transitionsAnnotation,
                                     Class<? extends Annotation> onEntrySelfTransitionAnnotation,
                                     Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) {

    Method[] methods = handler.getClass().getDeclaredMethods();
    Arrays.sort(methods, new Comparator<Method>() {
        @Override
        public int compare(Method m1, Method m2) {
            return m1.toString().compareTo(m2.toString());
        }
    });

    for (Method m : methods) {        
        // 做State与OnEntry,OnExit注解标注的方法进行绑定,在进入transition以及退出时候调用
        setupSelfTransitions(m, onEntrySelfTransitionAnnotation, onExitSelfTransitionAnnotation, states, handler);

        List<TransitionWrapper> transitionAnnotations = new ArrayList<>();
        // 这里是找带有指定的transitionAnnotation注解的方法
        // 如果是将其包装成TransitionWrapper进行保存,在下一步处理中好获取相应数据
        if (m.isAnnotationPresent(transitionAnnotation)) {
            transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m
                                                            .getAnnotation(transitionAnnotation)));
        }
        // 处理多个注解的
        if (m.isAnnotationPresent(transitionsAnnotation)) {
            transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation,
                                                                              transitionsAnnotation, m.getAnnotation(transitionsAnnotation)).value()));
        }

        if (transitionAnnotations.isEmpty()) {
            continue;
        }

        for (TransitionWrapper annotation : transitionAnnotations) {
            Object[] eventIds = annotation.on();

            if (eventIds.length == 0) {
                throw new StateMachineCreationException("Error encountered when processing method " + m
                                                        + ". No event ids specified.");
            }

            if (annotation.in().length == 0) {
                throw new StateMachineCreationException("Error encountered when processing method " + m
                                                        + ". No states specified.");
            }

            State next = null;

            if (!annotation.next().equals(Transition.SELF)) {
                next = states.get(annotation.next());

                if (next == null) {
                    throw new StateMachineCreationException("Error encountered when processing method " + m
                                                            + ". Unknown next state: " + annotation.next() + ".");
                }
            }

            for (Object event : eventIds) {
                if (event == null) {
                    event = Event.WILDCARD_EVENT_ID;
                }

                if (!(event instanceof String)) {
                    event = event.toString();
                }

                for (String in : annotation.in()) {
                    State state = states.get(in);

                    if (state == null) {
                        throw new StateMachineCreationException("Error encountered when processing method "
                                                                + m + ". Unknown state: " + in + ".");
                    }
                    // 这里就是执行State与Transition的绑定
                    // 定义了如下的关系:state执行了event 时候
                    // 使用什么样的transition进行处理
                    state.addTransition(new MethodTransition(event, next, m, handler), annotation.weight());
                }
            }
        }
    }
}

这段代码就是创建状态机的核心代码了,主要了是解析 @State 状态集合,解析 @Transition 标签,做 State与 Transition,Event 三者的绑定。当这三者关系确定后,状态机在使用的时候就只需要是先获取当前实体所属的 State,然后获取 State 上绑定的 Transition ,再利用 Transition 执行传入的 Event 即可。

对外服务的提供

// 创建对外服务对象
TaskWork taskWork = new StateMachineProxyBuilder().setStateContextLookup(new StateContextLookup() {
    @Override
    public StateContext lookup(Object[] objects) {
        Integer taskId = (Integer)objects[0];
        // 这里应该是根据Id去数据库查询
        Task task = new Task();
        task.setId(taskId);
        StateContext context = new DefaultStateContext();
        if (taskId == 123) {
            task.setState(TaskHandler.CREATED);
        } else if (taskId == 124) {
            task.setState(TaskHandler.TOOK);
        } else if (taskId == 125) {
            task.setState(TaskHandler.SUBMITTED);
        }
        context.setCurrentState(sm.getState(task.getState()));
        context.setAttribute("task", task);
        return context;
    }
}).create(TaskWork.class, sm);

可以看到状态机对外暴露的服务依然是最简单的 TaskWork 接口,而如何实现这样的功能呢?最容易想到的就是动态代理,Mina StateMachine 的确也是这样做的。

StateMachineProxyBuilder#create()

一起看下 StateMachineProxyBuilder#create() 方法,其实特别简单,就是通过动态代理模式创建了一个代理类。

public Object create(Class<?>[] ifaces, StateMachine sm) {
    ClassLoader cl = defaultCl;
    if (cl == null) {
        cl = Thread.currentThread().getContextClassLoader();
    }

    InvocationHandler handler = new MethodInvocationHandler(sm, contextLookup, interceptor, eventFactory,
                                                            ignoreUnhandledEvents, ignoreStateContextLookupFailure, name);
    return Proxy.newProxyInstance(cl, ifaces, handler);
}

MethodInvocationHandler

看动态代理对象,只需要关注其 Handler 即可,我们看下。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ... ...
    args = args == null ? EMPTY_ARGUMENTS : args;
    
    // 拦截器处理可以对输入参数做一些处理
    if (interceptor != null) {
        args = interceptor.modify(args);
    }
    // lookup方法去加载context
    StateContext context = contextLookup.lookup(args);

    if (context == null) {
        if (ignoreStateContextLookupFailure) {
            return null;
        }
        
        throw new IllegalStateException("Cannot determine state context for method invocation: " + method);
    }
    // 事件工厂去创建事件,statematchine其实最终还是由事件去驱动的
    Event event = eventFactory.create(context, method, args);

    try {
        // statemachine处理传入事件,触发状态流转
        sm.handle(event);
    } catch (UnhandledEventException uee) {
        if (!ignoreUnhandledEvents) {
            throw uee;
        }
    }

    return null;
}

这里可以看到主要是会由 StateContextLookup 去根据传入参数查找相应的 StateContext,然后由EventFactory 去创建一个 Event,最后 StateMachine 去处理这个事件来完成状态机的调用及内部状态的流转,状态机可以说是由事件去驱动的。

StateMachine#handler()

接着往下看 StateMachine#handler() 方法。

private void handle(State state, Event event) {
    StateContext context = event.getContext();
    // 获取state上面绑定的transitions 
    for (Transition t : state.getTransitions()) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Trying transition {}", t);
        }

        try {
            // transition实际执行相关的事件
            if (t.execute(event)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Transition {} executed successfully.", t);
                }
                // 执行成功执行onExits,OnEntries设置相应的状态
                setCurrentState(context, t.getNextState());

                return;
            }
        } 
        ... ...

    /*
     * No transition could handle the event. Try with the parent state if
     * there is one.
     */
     // 如果没有当前state没有绑定transition
     // 或者没有符合条件的transition,去找parent state
     // 如果还没有就是说明是不支持的,抛出没有处理的Envet异常
    if (state.getParent() != null) {
        handle(state.getParent(), event);
    } else {
        throw new UnhandledEventException(event);
    }
}

到这里就可以看到,事件最终的执行还是依托 Transition 去执行,这里获取了 State 上绑定的 Transition ,然后利用 Transition 去处理 Event。最后通过执行的结果,以及实际方法处理中抛出的:BreakAndContinueException,BreakAndGotoException,BreakAndCallException,BreakAndReturnException 异常来进行流程控制,Catch 过程的代码太长,可以去源码中详细看下。

MethodTransition#doExecute()

然后看下实际的 Transition 的处理过程,也就是 MethodTransition#doExecute() 方法。

public boolean doExecute(Event event) {
    Class<?>[] types = method.getParameterTypes();

    if (types.length == 0) {
        invokeMethod(EMPTY_ARGUMENTS);
        return true;
    }
    // 如果参数长度大于2+原始参数失败
    if (types.length > 2 + event.getArguments().length) {
        return false;
    }

    Object[] args = new Object[types.length];
    int i = 0;
    // 如果第一个参数是Event,则将event对象放入参数列表
    if (match(types[i], event, Event.class)) {
        args[i++] = event;
    }
    // 如果第二个参数是StateContext则将context对象放入参数列表
    if (i < args.length && match(types[i], event.getContext(), StateContext.class)) {
        args[i++] = event.getContext();
    }

    Object[] eventArgs = event.getArguments();
    // 判定剩余参数类型是否匹配,如果不匹配则执行失败
    for (int j = 0; i < args.length && j < eventArgs.length; j++) {
        if (match(types[i], eventArgs[j], Object.class)) {
            args[i++] = eventArgs[j];
        }
    }

    if (args.length > i) {
        return false;
    }
    // 执行method
    invokeMethod(args);

    return true;
}

这段代码并没有做什么实际的工作,主要做了参数的校验与绑定,对实际处理方法中如果加了 Event 或者是StateContext 也把相应的对象塞到参数列表里面,实际执行时候大概率也会用到 StateContext。

接着看最后调用的 invokeMethod() 方法。

private void invokeMethod(Object[] arguments) {
    try {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Executing method " + method + " with arguments " + Arrays.asList(arguments));
        }

        method.invoke(target, arguments);
    } catch (InvocationTargetException ite) {
        if (ite.getCause() instanceof RuntimeException) {
            throw (RuntimeException) ite.getCause();
        }

        throw new MethodInvocationException(method, ite);
    } catch (IllegalAccessException iae) {
        throw new MethodInvocationException(method, iae);
    }
}

invokeMethod() 方法是实际的方法执行类,这里会对 Exception 区别处理,如果是 RuntimeException 将直接抛出,这里处理成 BreakException 是不更好点?

到这里整个调用过程也分析完了,可以看到状态机的流转主要是由 Event 驱动,状态机获取 State 后,使用上面绑定的 Transition 来处理 Event 事件。StateMachineProxyBuilder 就是用动态代理的方式,提供了简便的对外接口。

其他使用方式

如果不使用 StateMachineProxyBuilder 生成代理类,也照样可以玩转状态机,这个可能更符合状态机的原始语义,只不过看起来不是那么友好,如前面这段示例程序:

StateContext context = new DefaultStateContext();
context.setCurrentState(sm.getState(TaskHandler.CREATED));
context.setAttribute("task", new Task());
Event event = new Event("take", context, new Object[]{123, "Jack"});
sm.handle(event);

可以看出 Mina StateMachine 的设计还是很清晰、巧妙的。运用了工厂模式、代理模式等设计模式,对外提供简单易懂的 API,内部流转也清晰明了,还是很值得学习一下的。

相关文章

网友评论

      本文标题:Mina StateMachine

      本文链接:https://www.haomeiwen.com/subject/zyiiprtx.html