Mina状态机介绍


Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . Mina 状态机介绍 (Introduction to mina-statemachine) 如果 你使用 Mina 开发 一个 复杂 的网络 应用 时, 你可 能在 某些 地方会 遇到 那个 古老而 又好 用 的状 态模 式, 来 使 用这 个模 式解 决你的 复杂 应用 。 然 而, 在 你做 这个 决定 之前, 你 或 许 想 检 出Mina 的状 态机 的代 码, 它会 根据 当前对 象的 状态 来返回 对接 收到 的简短 的数 据的 处理 信 息。 注意: 现在正式发布 Mina 的状态 机。因此你 要自己在 Mina 的SVN 服 务 器上 检 出 该 代码 , 并自 己编 译, 请 参 考 开发指 南, 来 获取 更多 的关于 检出 和编译 Mina 源码 的信 息。 Mina 的状 态机 可以 和所 有已经 发布 的版本 Mina 配合 使用 (1.0.x, 1.1.x 和当前 发布 的版 本 )。 一个简单的例子一个简单的例子一个简单的例子一个简单的例子 让我 们使 用一个 简单 的例 子来 展示一下 Mina 的状 态机 是如 何工作 的。 下 面的图 片展 示了 一 个录 音机 的状 态机。 其 中 的椭 圆是 状态, 箭 头 表示 事务 。 每 个 事务 都有 一个事 件的 名字 来 标 记该 事务 。 初始 化时 , 录 音 机的状 态是 空的 。 当 磁 带放如 录音 机的 时候, 加 载的 事件 被触发 , 录 音 机 进入 到加 载状 态。 在 加载 的状 态下 , 退 出 的事 件会 使录 音机进 入到 空的 状态, 播 放 的事 件 会 使加 载的 状态 进入到 播放 状态 。等等 ......我想 你可 以推 断后后 面的 结果 :) 现在 让我 们写 一些代 码。 外部 (录音 机中 使用 该代码 的地 方 )只能 看到 录音 机的接 口: ----------------------START---------------------- public interface TapeDeck { void load(String nameOfTape); void eject(); void start(); void pause(); void stop(); } -----------------------END------------------------ 下面 我们 开始 编写真 正执 行的 代码, 这 些 代码 在一个 事务 被触 发时 , 会 在 状态 机中 执行 。 首 先我 们定 义一个 状态 。这 些状 态都使 用字 符串 常量来 定义 ,并 且使用 @state 标记 来声 明。 ----------------------START---------------------- public class TapeDeckHandler { Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; } -----------------------END------------------------ 现在 我们 已经 定义了 录音 机中 的所有 状态 , 我 们 可 以根据 每个 事务 来创建 相应 的代 码。 每 个 事务 都和 一个 TapeDeckHandler 的方 法对 应。 每个事 务的 方法 都使用 @Transtration 标签 来 声明 , 这个标 签定 义了 事件的 ID,该 ID会触 发事 务的 执行。 事务 开始 时的 状态使用 start, 事务 结束 使用 next,事 务正 在运 行使用 on。 ----------------------START---------------------- public class TapeDeckHandler { @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; @Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) { System.out.print ln("Tape '" + nameOfTape + "' loaded"); } @Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape() { System.out.print ln("Playing tape"); } @Transition(on = "pause", in = PLAYING, next = PAUSED) public void pauseTape() { System.out.print ln("Tape paused"); } @Transition(on = "stop", in = PLAYING, next = LOADED) public void stopTape() { System.out.print ln("Tape stopped"); } @Transition(on = "eject", in = LOADED, next = EMPTY) public void ejectTape() { System.out.print ln("Tape ejected"); } Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . } -----------------------END------------------------ 请注 意, TapeDeckHandler 类没 有实现 TapeDeck ,呵 呵, 这是 故意的 。 现在 让我 们亲 密接触 一下 这个 代码。在 loadTape 方法 上的 @Transition 标签 : ----------------------START---------------------- @Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) {} -----------------------END----------------------- 指定了 这个状态后 ,当录音 机处于空状 态时,磁带 装载事件 启动后会触发 loadTape 方法, 并且录音机状态将会变换到Loaded 状态。@Transition 标签中关于 pauseTape,stopTape,ejectTape 的方 法就 不需 要在多 介绍 了。 关于 playTape 的标 签和 其他 的 标签 看起 来不 太一样 。 从 上 面的 图中 我们可 以知 道, 当 录 音 机的状 态在 Loaded或者 Paused 时, play 事件 都会 播放 磁带。 当多 个事 务同时 条用 同一个 方法 时, @Transition 标签 需要 按 下面 的方 法使 用: ----------------------START---------------------- @Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape(){} -----------------------END----------------------- @Transition 标签 清晰 的列 出了声 明的 方法 被多个 事务 调用 的情况 。 ###################################################### 要点 : 更多关于 @Transition 标签的参数 (1)如果你省略了 on 参数,系统 会将该值默认为“*”,这样任何事件都可以触发 该方法。 (2)如果你省略了 next 参数,系统 会将默认值改为 “_self_ ”,这个是和当前的状态相关的, 如果你要实现一个 循环的事务,你所需要做的就是省略状态机中的 next 参数。 (3)weight 参数用于定义事务的查 询顺序,一般的状态的事务 是根据 weight 的值 按升序排列的, weight 默认的是 0. ###################################################### 现在 最后 一步 就是使 用声 明类 创建一个 状 态机 的对象 , 并 且 使用这 个状 态机 的实例 创建 一 个 代理 对象 ,该 代理对 象实 现了 TapeDeck接口 : ----------------------START---------------------- public static void main(String[] args) { // 创建 录音 机事 件的句 柄 TapeDeckHandler handler = new TapeDeckHandler(); // 创建 录音 机的 状态机 StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); // 使用 上面 的状 态机, 通过 一个 代理创 建一个 TapeDeck的实 例 TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm); Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . // 加载 磁带 deck.load("The Knife - Silent Shout"); // 播放 deck.play(); // 暂停 deck.pause(); // 播放 deck.play(); // 停止 deck.stop(); // 退出 deck.eject(); } -----------------------END----------------------- 这一行 ----------------------START---------------------- TapeDeckHandler handler = new TapeDeckHandler(); StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); -----------------------END----------------------- 使用 TapeDeckHandler 创建 一个 状态 机的实 例。 StateMachineFactory.getInstance(...) 调用 的方 法中 使用的 Transition.class 是通 知工 厂我 们使用 @Transition 标签 创建 一个 状态机 。 我 们指 定了 状态 机开始 时状 态是 空的。 一 个 状态 机是 一个基 本的 指示 图。 状 态 对象和 图中 的 节 点对应,事 务对 象和 箭头 指向 的方 向对 应。 我们 在 TapeDeckHandler 中使用的 每一个 @Transition 标签 都和 一个 事务的 实例 对应 。 ###################################################### 要点 : 那么, @Transition 和 Transition 有什么不同吗? @Transition 是你用来标记当事务在状态之间变化时 应该使用那个方法。在后台处理中, Mina 的状态机会为 MethodTransition 中每一个事务标签创建一个事务的实例。 MethodTransition 实现了 Transition 接口。作为一个 Mina 状态机的使用者,你不用直接使用 Transition 或者 MethodTransition 类型的对象。 ###################################################### 录音机 TapeDeck 的实 例是 通过 调用 StateMachineProxyBuilder 来创 建的 : ----------------------START---------------------- TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm); -----------------------END----------------------- StateMachineProxyBuilder.create()使用 的接 口需 要由代 理的 对象 来实现 , 状 态机 的实 例将 接 收由 代理 产生 的事件 所触 发的 方法。 当代 码执 行时 ,输出 的结 果如 下: ----------------------START---------------------- Tape 'The Knife - Silent Shout' loaded Playing tape Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . Tape paused Playing tape Tape stopped Tape ejected -----------------------END----------------------- ##################################################### 要点: 这和 Mina 有什么关系? 或许你已经注意到,在这个例子中没有对 Mina 进行任何配置。但 是不要着急。 稍后我们将 会看到如何为 Mina 的IoHandler 接口创建一个状态机。 ###################################################### 它是怎样工作的?它是怎样工作的?它是怎样工作的?它是怎样工作的? 让我 们走 马观 花的看 看当 代理 调用一个 方 法的 时候发 生了 什么 。 查看 一个 StateContext(状态 的上 下文 )对象 状态 上下 文之 所以重 要是 因为 它保存 了当 前的 状态。 代理 调用 一个方 法时 ,状 态上下 文 会通知 StateContextLookup 实例 去方 法的 参数中 获取 一个 状态的 上下 文。 一般情 况下 , StateContextLookup 的实 现将 会循 环方法 中的 参数 ,并查 找一个 指 定类型 的对 象, 并且 使用 这个 对象 反转出 一个 上下 文对象 。 如 果没 有声 明一个 状态 上下 文, StateContextLookup 将会 创一个 , 并将其 存放 到对 象中。 当代理 Mina 的IoHandler 接口时 ,我们将使用 IoSessoinStateContextLookup 实例, 该实 例用来 查询一个 IoSession 中的方 法参数。它 将会使用 IoSession 的属性 值为每一个 Mina 的session 来存 放一个 独 立的状 态上 下文 的实例 。 这 中方 式下, 同 样 的状 态机可 以让 所有 的 Mina 的会 话使 用, 而不会 使每 个会 话彼此 产生 影响 。 ###################################################### 要点 : 在上面的例子中,当我们使用 StateMachineProxyBuilder 创建一个代 理时,我们 一直没有我们一直没有配置 StateContex tLookup 使用哪种实现。如果没 有配置,系统会 使用 SingletonStateContextLookup 。SingletonStateContextLookup 总是不理会方法中 传递给它的参数,它一直返回一个 相同的状态上下文。很明显,这中方式在多个客户端 并发的情况下使用同一个同一个状态机 是没有意义的。这种情况下的配置会 在后面的关于 IoHandler 的代理配置时进行说明。 ###################################################### 将方法 请求反转成 一个事件 对象所有在 代理对象上 的方法请 求都会有代 理对象转换 成事件 对象 。一个事 件有一个 ID或者 0个或 多个参数 。事件的 ID和方 法的名字 相当, 事件的参 数和方法的参数相 当。调用 方 法 deck.load("The Knife - Silent Shout") 相当于事件 {id = "load", arguments = ["The Knife - Silent Shout"]}.事件对象中 包含一 个状 态上 下文的引 用, 该状 态上 下文是 当前 查找 到的。 触发状态机触发状态机触发状态机触发状态机 一旦事件对象被创建,代理会调用StateMachine.handle(Event). 方法。 StateMachine.handle(Event)遍历 事务对 象中当 前的 状态, 来查找 能够接 收当前 事件 的事务 的实 例。 这 个 过程 会在事 务的 实例 找到后 停止 。 这 个 查 询的 顺序是 由事 务的 重量值 来决 定 的 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . (重 量值 一般 在 @Transition 标签中指定)。 执行事务执行事务执行事务执行事务 最后 一部 就是在 Transition 中调 用匹 配事 件对象的 Transition.execute(Event)方法 。当 事件 已经 执行 ,这 个状态 机将 更新 当前的 状态 ,更 新后的 值是 你在 事务中 定义 的后 面的状 态。 ###################################################### 要点 : 事务是一个 接口。每次 你使用 @Transition 标签时, MethodTransition 对象将会被创建。 ###################################################### MethodTransition(MethodTransition(MethodTransition(MethodTransition(方法事务方法事务方法事务方法事务)))) MethodTransition 非常 重要 , 它 还需 要一些 补 充说明 。 如 果 事件 ID和@Transition 标签 中 的 on 参数 匹配 , 事 件 的参 数和 @Transition 中的 参数 匹配 , 那么 MethodTransition 和这 个事 件 匹配 。 所以 如果 事件 看起来 像 {id = "foo", arguments = [a, b, c]},那 么下 面的 方法: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(One one, Two two, Three three) {...} -----------------------END----------------------- 只和 这个 事 件 匹 配 ((a instanceof One && b instanceof Two && c instanceof Three) == true).。当 匹配 时, 这个方 法将 会被 与其匹 配的 事件 使用绑 定的 参数 调用。 ###################################################### 要点 : Integer, Double, Float, 等也和他们的基本类型 int, double, float, 等匹配。 ###################################################### 因此 ,上 面的 状态是 一个 子集 ,需要 和下 面的 方法匹 配: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(Two two) {...} -----------------------END----------------------- 上面 的方法 和 ((a instanceof Two || b instanceof Two || c instanceof Two) == true)是等 价 的。 在这 种情 况下, 第一个 被 匹配的 事件 的参 数将会 和该 方法 绑定, 在它 被调 用的时 候。 一个 方法 如果 没有参 数, 在其 事件的 ID匹配 时, 仍然 会被调 用: ----------------------START---------------------- @Transition(on = "foo") public void someMethod() {...} -----------------------END----------------------- 这样 做让 事件 的处理 变得 有点 复杂, 开 始 的 两个方 法的 参数 和事件 的类 及状 态的上 下文 接 口 相匹 配。 这意 味着: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(Event event, StateContext context, One one, Two two, Three three) {...} Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @Transition(on = "foo") public void someMethod(Event event, One one, Two two, Three three) {...} @Transition(on = "foo") public void someMethod(StateContext context, One one, Two two, Three three) {...} -----------------------END----------------------- 上面 的 方 法 和 事 件 {id = "foo", arguments = [a, b, c]} if ((a instanceof One && b instanceof Two&& c instanceof Three) == true) 是匹 配的 。当 前的事 件对 象和 事件的 方法 绑定 ,当 前的 状态上 下文 和该 方法被 调用 时的 上下文 绑定 。 在此 之前 一个 事件的 参数 的集 合将会 被使 用。 当 然 , 一 个 指 定的状 态上 下文 的实现 将会 被 指 定, 以用 来替 代通用 的上 下文 接口。 ----------------------START---------------------- @Transition(on = "foo") public void someMethod(MyStateContext context, Two two) {...} -----------------------END----------------------- ###################################################### 要点 : 方法中参数的顺序很重要。若方法需要 访问当前的事件,它必须被配置为第一个 方法参数。当事件为第一个 参数的时候,状态上下问不能配置为第二个参数,它也不能 配置为第一个方法的参 数。事件的参数也要按正确的顺序进行匹配。方法的事务不会在 查找匹配事件方法的时 候重新排序。 ###################################################### 到现 在如 果你 已经掌 握了 上面 的内容 , 恭 喜你 ! 我 知 道 上面的 内容 会有 点难以 消化 。 希 望 下 面的例子能让你对上面的内容有更清晰的了解。注意这个事件Event {id = "messageReceived", arguments = [ArrayList a = [...], Integer b = 1024]}。下 面的方法 将 和这 个事 件是 等价的 : ----------------------START---------------------- // All method arguments matches all event arguments directly @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Integer i) {...} // Matches since ((a instanceof List && b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(List l, Number n) {...} // Matches since ((b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(Number n) {...} // Methods with no arguments always matches @Transition(on = "messageReceived") public void messageReceived() {...} // Methods only interested in the current Event or StateContext always matches @Transition(on = "messageReceived") Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . public void messageReceived(StateContext context) {...} // Matches since ((a instanceof Collect ion) == true) @Transition(on = "messageReceived") public void messageReceived(Event event, Collect ion c) {...} -----------------------END----------------------- 但是 下面 的方 法不会 和这 个事 件相匹 配: ----------------------START---------------------- // Incorrect ordering @Transition(on = "messageReceived") public void messageReceived(Integer i, List l) {...} //((a instanceof LinkedList) == false) @Transition(on = "messageReceived") public void messageReceived(LinkedList l, Number n) {...} // Event must be first argument @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Event event) {...} // StateContext must be second argument if Event is used @Transition(on = "messageReceived") public void messageReceived(Event event, ArrayList l, StateContext context) {...} // Event must come before StateContext @Transition(on = "messageReceived") public void messageReceived(StateContext context, Event event) {...} -----------------------END----------------------- 状态继承状态继承状态继承状态继承 状态 的实 例将 会有一个 父 类的 状态。 如果 StateMachine.handle(Event)的方 法不 能找 到一 个 事务 和当 前的 事件在 当前 的状 态中匹 配, 它 将 会寻 找父类 中的 装。 如 果 仍然 没有找 到, 那 么 事务 将会 自动 寻找父 类的 父类 ,知道 找到 为止 。 这个 特性 很有 用, 当 你想 为所 有的 状态添 加一些 通 用的代 码时 , 不 需 要 为每 一个状 态的 方 法 来声 明事 务。 这里你 可以 创建 一个类 的继 承体 系,使 用下 面的 方法即 可: ----------------------START---------------------- @State public static final String A = "A"; @State(A) public static final String B = "A->B"; @State(A) public static final String C = "A->C"; @State(B) public static final String D = "A->B->D"; @State(C) public static final String E = "A->C->E"; -----------------------END----------------------- 使用 状态 继承 来处理 错误 信息 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 让我 们回 到录 音机的 例子 。 如 果录 音机 里没有 磁带 , 当 你调用 deck.play()方法 时将 会怎 样 ? 让我 们试 试: 示例 代码 : ----------------------START---------------------- public static void main(String[] args) { ... deck.load("The Knife - Silent Shout"); deck.play(); deck.pause(); deck.play(); deck.stop(); deck.eject(); deck.play(); } -----------------------END----------------------- 运行 结果 : ----------------------START---------------------- ... Tape stopped Tape ejected Exception in thread "main" o.a.m.sm.event.UnhandledEventException: Unhandled event: org.apache.mina.statemachine.event.Event@15eb0a9[id=play,...] at org.apache.mina.statemachine.StateMachine.handle(StateMachine.java:285) at org.apache.mina.statemachine.StateMachine.processEvents(StateMachine.java:142) ... -----------------------END----------------------- 哦, 我 们 得到了 一个 无法 处理的 异常 UnhandledEventException, 这 是因 为在 录音机 的空 状 态 时, 没有事 务来 处理 播放 的 状态 。 我们 将添 加一 个指 定 的事 务来 处理 所 有不 能匹 配的 事 件。 ----------------------START---------------------- @Transitions({ @Transition(on = "*", in = EMPTY, weight = 100), @Transition(on = "*", in = LOADED, weight = 100), @Transition(on = "*", in = PLAYING, weight = 100), @Transition(on = "*", in = PAUSED, weight = 100) }) public void error(Event event) { System.out.print ln("Cannot '" + event.getId() + "' at this time"); } -----------------------END----------------------- 现在 当你 运行 上面的 main()方法 时, 你将 不会再 得到 一个 异常, 输出 如下 : ----------------------START---------------------- Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . ... Tape stopped Tape ejected Cannot 'play' at this time. -----------------------END----------------------- 现在 这些 看起 来运行 的都 很好 , 是吗? 但是 如果 们有 30 个状 态而 不是 4个, 那 该怎 么办 ? 那么 我们 需要 在上面 的错 误方 法处理 中配置 30 个事 务的 声明 。这 样不 好。 让我 们用状 态 继 承来 解决 : ----------------------START---------------------- public static class TapeDeckHandler { @State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused"; ... @Transition(on = "*", in = ROOT) public void error(Event event) { System.out.print ln("Cannot '" + event.getId() + "' at this time"); } } -----------------------END----------------------- 这个 运行 的结 果和上 面的 是一样 的, 但是 它比 要每个 方法 都配 置声明 要简 单的 多。 MinaMinaMinaMina的状态机和的状态机和的状态机和的状态机和IoHandler IoHandler IoHandler IoHandler 配合使用配合使用配合使用配合使用 现在 我们将 上面的 录音 机程序 改造成 一个 TCP服务 器,并 扩展一些 方 法。服 务器将 接收一 些命 令类 似于 : load , play, stop等等 。 服 务 器 响应的 信息 将会 是 + 或 者是 - 。 协议是 基于 Mina 自身 提供 的一个 文本 协议 ,所 有的 命令和 响应 编码 都 是基于 UTF-8。这 里有 一个 简单的 会话 示例 : ----------------------START---------------------- telnet localhost 12345 S: + Greetings from your tape deck! C: list S: + (1: "The Knife - Silent Shout", 2: "Kings of convenience - Riot on an empty street") C: load 1 S: + "The Knife - Silent Shout" loaded C: play S: + Playing "The Knife - Silent Shout" C: pause S: + "The Knife - Silent Shout" paused C: play S: + Playing "The Knife - Silent Shout" C: info S: + Tape deck is playing. Current tape: "The Knife - Silent Shout" Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . C: eject S:- Cannot eject while playing C: stop S: + "The Knife - Silent Shout" stopped C: eject S: + "The Knife - Silent Shout" ejected C: quit S: + Bye! Please come back! -----------------------END----------------------- 该程序完整的代码在 org.apache.mina.example.tapedeck 包中,这个可以通过检出 Mina 源码的 SVN 库中的 mina-example 来得 到。 代 码 使用 Mina 的ProtocolCodecFilter 来编 解 码 传输 的二 进数 据对象 。 这 里只 是为 每个状 态对 服务 器的请 求实 现了 一个简 单的 编解 码器。 在 此不 在对 Mina 中编 解码 的实 现做过 多的 讲解 。 现在 我们看 一下这 个服 务器是 如何工 作的。 这里面 一个 重要的 类就是 实现了 录音机 程序的 TapeDeckServer 类。 这里 我们 要做的 第一件 事 情就是 去定 义这 些状态 : ----------------------START---------------------- @State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused"; -----------------------END----------------------- 在这 里没 有什 么新增 的内 容。 然 而 , 但 是 处理 这些 事件 的方法 看起 来将 会不一样 。 让 我 们 看 看playTape 的方 法。 ----------------------START---------------------- @IoHandlerTransitions({ @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING), @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING) }) public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { session.write("+ Playing \"" + context.tapeName + "\""); } -----------------------END----------------------- 这里没有使 用通 用的 @Transition 和@Transitions 的事务声明 ,而 是使 用 了 Mina 指定的 @IoHandlerTransition 和@IoHandlerTransitions 声明 。当为 Mina 的IoHandler 创建 一个状 态机时 ,它会选择 让你使用 Java enum (枚举 )类型来替 代我们上 面使用的字 符串类型。 这个在 Mina 的IoFilter 中也 是一样 的 。 我们现在使用MESSAGE_RECEIVED 来替代"play" 来作为事件的名字(on 是 @IoHandlerTransition 的一个 属 性 )。这 个 常量 是 在 org.apache.mina.statemachine.event.IoHandlerEvents 中定 义 的 , 它 的 值 是 "messageReceived", 这 个和 Mina 的IoHandler 中的 messageReceived()方法 是一致 的 。 谢 谢Java 5中的静态 导入,我们在使 用该变量的时候 就不用再通过类 的名字来调用该 常量, Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 我们 只需 要按 下面的 方法 导入 该类: ----------------------START---------------------- import static org.apache.mina.statemachine.event.IoHandlerEvents.*; -----------------------END----------------------- 这样 状态 内容 就被导 入了 。 另外一个要改变的内容是我们自定了一个StateContext 状态上下文的实现-- TapeDeckContext。这 个类 主要 是用于 返回 当前 录音机 的状 态的 名字。 ----------------------START---------------------- static class TapeDeckContext extends AbstractStateContext { public String tapeName; } -----------------------END----------------------- ###################################################### 要点 : 为什么不把状态的名字保存到 IoSession 中? 我们可以将录音机状态的名字保存到 IoSession 中,但是使用一个自定义的 StateContex t 来保存这个状态将 会使这个类型更 加安全。 ###################################################### 最后 需要 注意 的事情是 playTape()方法 使用了 PlayCommand命令 来作 为它 的最后 的一个 参 数。 最后一个 参数和 IoHandler's messageReceived(IoSession session, Object message)方 法匹 配。 这 意 味着 只有 在客 户端发 送的 信息 被编码成 playCommand 命令 时, 该 方 法才 会 被 调用 。 在录 音机 开始 进行播 放前 , 它 要 做 的事情 就是 要装 载磁带 。 当 装载 的命 令从 客户端 发送 过 来 时, 服务 器提 供的磁 带的 数字 代号将 会从 磁带 列表中 将可 用的 磁带的 名字 取出 : ----------------------START---------------------- @IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) public void loadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) { if (cmd.getTapeNumber() < 1 || cmd.getTapeNumber() > tapes.length) { session.write("- Unknown tape number: " + cmd.getTapeNumber()); StateControl.breakAndGotoNext(EMPTY); } else { context.tapeName = tapes[cmd.getTapeNumber() - 1]; session.write("+ \"" + context.tapeName + "\" loaded"); } } -----------------------END----------------------- 这段 代码 使用了 StateControl 状态 控制 器来 重写了 下一个 状 态。 如 果 用户 指定 了一个 非法 的 数字 ,我 们将 不会将 加载 状态 删除, 而是 使用 一个空 状态 来代 替。代 码如 下所 示: ----------------------START---------------------- StateControl.breakAndGotoNext(EMPTY); -----------------------END----------------------- 状态 控制 器将 会在后 面的 章节 中详细 的讲 述。 connect()方法 将会在 Mina 开启 一个 会话 并调用 sessionOpened()方法 时触 发。 ----------------------START---------------------- Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) public void connect(IoSession session) { session.write("+ Greetings from your tape deck!"); } -----------------------END----------------------- 它所 做的 工作 就是向 客户 端发 送欢迎 的信 息。 状态机 将会 保持 空的状 态。 pauseTape(), stopTape() 和ejectTape() 方法 和 playTape()很相 似。 这 里 不再进 行过 多 的 讲述 。 listTapes(), info() 和quit() 方法 也很容易 理,也 不再进行 过多的 讲解。请 注意后 面的 三个 方法 是在根 状态 下使 用的。 这 意 味着 listTapes(), info() 和quit() 可以 在任 何 状 态中 使用 。 现在 让我 们看 一下错 误处 理。 error()将会 在客 户端 发送一个 非 法的 操作时 触发 : ----------------------START---------------------- @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT, weight = 10) public void error(Event event, StateContext context, IoSession session, Command cmd) { session.write("- Cannot " + cmd.getName() + " while " + context.getCurrentState().getId().toLowerCase()); } -----------------------END----------------------- error()已经 被指定 了一个 高于 listTapes(), info() 和quit() 的重 量值来 阻止客 户端 调用上 面的 方法 。 注意 error()方法 是怎 样使 用状态 上下 文来 保存当 前状 态的 ID的。 字 符 串常 量 值 由@State annotation (Empty, Loaded etc) 声明 。 这 个 将会由 Mina 的状 态机 当成 状态的 ID 来使 用。 commandSyntaxError()方法将会在 ProtocolDecoder 抛出 CommandSyntaxException 异常 时被 调用 。它 将会简 单的 输出 客户 端发 送的 信息不 能解 码为 一个状 态命 令。 exceptionCaught() 方法 将会 在任 何异常 发生 时调 用, 除了 CommandSyntaxException 异常 (这个 异常 有一个 较高 的重 量值 )。 它将 会立 刻关 闭会话 。 最后一个@IoHandlerTransition 的方法是unhandledEvent() ,它将会在 @IoHandlerTransition 中的 方法 没有 事件匹 配时 调用 。 我们 需要 这个 方法是 因为 我们 没有 @IoHandlerTransition 的方 法来 处理 所有可 能的 事件 (例 如: 我们 没有 处理 messageSent(Event) 方法 )。没 有这 个方 法, Mina 的状 态机 将会 在执行 一个 事件 的时候 抛出 一个 异常。 最后 一点 我们 要看的 是那 个类 创建了 IoHandler 的代 理, main()方法 也在 其中 : ----------------------START---------------------- private static IoHandler createIoHandler() { StateMachine sm = StateMachineFactory.getInstance(IoHandlerTransition.class).create(EMPTY, new Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . TapeDeckServer()); return new StateMachineProxyBuilder().setStateContextLookup( new IoSessionStateContextLookup(new StateContextFactory() { public StateContext create() { return new TapeDeckContext(); } })).create(IoHandler.class, sm); } // This code will work with MINA 1.0/1.1: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new SocketAcceptor(); SocketAcceptorConfig config = new SocketAcceptorConfig(); config.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); config.getFilterChain().addLast("codec", pcf); acceptor.bind(new InetSocketAddress(12345), createIoHandler(), config); } // This code will work with MINA trunk: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); acceptor.getFilterChain().addLast("codec", pcf); acceptor.setHandler(createIoHandler()); acceptor.setLocalAddress(new InetSocketAddress(PORT)); acceptor.bind(); } -----------------------END----------------------- createIoHandler() 方法 创建了 一个状 态机 ,这个 和我们 之前所 做的相 似除 了我们 指定一个 IoHandlerTransition.clas 类来 代替 Transition.class 在StateMachineFactory.getInstance(...) 方法 中。 这是 我们在 使用 @IoHandlerTransition 声明 的时 候必 须要做 的。 当然 这时我 们使 用了 一个 IoSessionStateContextLookup 和一个 自 定义的 StateContextFactory 类,这个在我 们创建一个IoHandler 代理时被使用到了。如果我们没有使用 IoSessionStateContextLookup , 那么所 有的 客户 端将会 使用 同一个 状态 机, 这是 我们 不 希 望看 到的 。 main()方法 创建了 SocketAcceptor实例 ,并且 绑定了 一个 ProtocolCodecFilter ,它 用于编 解码命 令对象。最 后它绑定了 12345 端口和 IoHandler 的实例 。这个 IoHandler 实例是由 createIoHandler()方法 创建 的。 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit .
还剩14页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

cooltaoism

贡献于2011-07-25

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf