这里主要讲下对外接口暴露的处理。

 // 创建对外接口对象
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);

这里主要看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);
}
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("hashCode".equals(method.getName()) && args == null) {
return Integer.valueOf(System.identityHashCode(proxy));
} if ("equals".equals(method.getName()) && args.length == 1) {
return Boolean.valueOf(proxy == args[0]);
} if ("toString".equals(method.getName()) && args == null) {
return (name != null ? name : proxy.getClass().getName()) + "@"
+ Integer.toHexString(System.identityHashCode(proxy));
} if (log.isDebugEnabled()) {
log.debug("Method invoked: " + method);
} 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去处理这个事件来完成状态机的调用,内部状态的轮转,状态机最终还是由事件去驱动的。


    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然后去处理event,然后通过执行的结果,以及实际方法处理中抛出的:BreakAndContinueException,BreakAndGotoException,BreakAndCallException,BreakAndReturnException异常来进行流程控制。

看一下实际的transition处理类,MethodTransition

    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。

    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就是用代理的方式提供了方便的对外接口类。
如果不使用这个也照样可以玩转状态机,如前面这段示例程序:
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);

可以看到整个状态机的设计还是很清晰、巧妙的。运用了工厂模式、代理模式等设计模式,对外提供简单易懂的API,内部流转也清晰明了,还是很值得我们学习的。

mina statemachine解读(二)的更多相关文章

  1. mina statemachine解读(一)

      statemachine(状态机)在维护多状态数据时有非常好的作用,现在github上star排名最前的是squirrel-foundation以及spring-statemachine,而min ...

  2. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  3. java多线程解读二(内存篇)

    线程的内存结构图 一.主内存与工作内存 1.Java内存模型的主要目标是定义程序中各个变量的访问规则.此处的变量与Java编程时所说的变量不一样,指包括了实例字段.静态字段和构成数组对象的元素,但是不 ...

  4. mybatis源码解读(二)——构建Configuration对象

    Configuration 对象保存了所有mybatis的配置信息,主要包括: ①. mybatis-configuration.xml 基础配置文件 ②. mapper.xml 映射器配置文件 1. ...

  5. java8完全解读二

    继续着上次的java完全解读一 继续着上次的java完全解读一1.强大的Stream API1.1什么是Stream1.2 Stream操作的三大步骤1.2.1 创建Stream1.2.2 Strea ...

  6. (转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...

  7. Mina使用总结(二)Handler

    Handler的基本作用,处理接收到的客户端信息 一个简单的Handler实现如下: package com.bypay.mina.handler; import java.util.Date; im ...

  8. cJONS序列化工具解读二(数据解析)

    cJSON数据解析 关于数据解析部分,其实这个解析就是个自动机,通过递归或者解析栈进行实现数据的解析 /* Utility to jump whitespace and cr/lf *///用于跳过a ...

  9. NSObject头文件解析 / 消息机制 / Runtime解读 (二)

    本章接着NSObject头文件解析 / 消息机制 / Runtime解读(一)写 给类添加属性: BOOL class_addProperty(Class cls, const char *name, ...

随机推荐

  1. Elasticsearch 通关教程(六): 自动发现机制 - Zen Discoveryedit

    发现方式 Zen discovery是内建的.默认的.用于Elasticsearch的发现模块.它提供了单播和基于文件的发现,可以通过插件扩展到支持云环境和其他形式的发现. Zen Discovery ...

  2. Nginx+Django-Python+BPMN-JS的整合工作流实战项目

    前言 找一个好用的画图工具真心不容易,Activiti 工作流自带的 Web 版画图工具,外表挺华丽,其实使用起来各种拧巴:Eclipse 的 Activiti 画图插件,对于相对复杂的流程也是很不友 ...

  3. wget在linux中安装出现错误解决办法

    在使用wget命令报错 certificate common name 'xxx' doesn't match requestde host name,我们一般的解决办法是查找下载地址,但是有时候更换 ...

  4. PS教程:抠透明冰块

    1.打开我们需要用到的素材,ctrl+a全选,ctrl+c复制 2.接下来给图层添加一个图层蒙版,按alt键点击图层蒙版,看到它变成白色了 3.ctrl+v,把刚才复制好的图粘贴进来 4.接着ctrl ...

  5. HTML、CSS、JS中常用的东西在IE中兼容问题汇总

    1.因为国内360浏览器.QQ浏览器等更新较快,所以不考虑Chrome支持某个css与否,因为一般都支持. 2.因为火狐等使用的人较少,且更新较快,所以不考虑支持与否,因为一般都支持 3.主要就是汇总 ...

  6. asp.net获取当前请求的url

    asp.net获取当前请求的url 设当前页完整地址是:http://www.dgshop.com/Home/Manager?id=2&para=ASFDG [1]获取 完整url 代码如下: ...

  7. Flutter获取屏幕宽高和Widget大小

    我们平时在开发中的过程中通常都会获取屏幕或者 widget 的宽高用来做一些事情,在 Flutter 中,我们可以使用如下方法来获取屏幕或者 widget 的宽高. MediaQuery 一般情况下, ...

  8. MyBatis 3源码解析(一)

    一.SqlSessionFactory 对象初始化 //加载全局配置文件 String resource = "mybatis-config.xml"; InputStream i ...

  9. PowerBi利用Python Script绕过ODBC来导入MongoDB数据

  10. 如何获得select被选中option的value和text

    如何获得select被选中option的value和text 一:JavaScript原生的方法 1:拿到select对象: var myselect=document.getElementById( ...