Tapestry 5 (本文针对5.1这个版本)改变了事件处理机制,不再需要将事件绑定到某个组件,而是在事件监听函数处定义需要监听什么样的事件。比如说产生事件的组件或者什么样类型的组件。

网上对于Tapestry 5事件的命名,传递等的文章有很多,Tapestry的官方网站上也很详细,我就不再鳌述这些内容了。本文讲一点深入的处理机制,也就是Tapestry 5 如何实际的将一个事件传递到某个方法。下面我们从事件处理机制,流程分析,动态参数解决方法几个方面进行讨论。

1. 事件处理机制

Tapestry采用了一种自底向上的事件搜索机制,即从发出事件的组件开始,逐层往上搜索事件处理函数,直到某个函数宣布事件失效为止。对于Tapestry这种利用模板构建的静态组件树而言,自底向上的搜索并不是一件复杂的事情,所以本文并不详细讨论这个问题。而将注意力集中在单个组件是如何处理事件的机制上。

为了能让组件能接收到事件,Tapestry框架首先会为每个组件生成一个子类,这个子类是实现了org.apache.tapestry5.runtime.Component接口的。在这个接口里定义了一个方法 “boolean dispatchComponentEvent(ComponentEvent event)“。也就是说实际上当有事件发生时,Tapestry框架就会调用逐渐的这一个接口来响应事件。(有关http请求是如何最终转变化组件事件请求的流程可以参见我另一篇博文《解读Tapestry5.1——请求调用链》)

我们先看一下“ComponentEvent ”的结构

[java] view plaincopy
  1. package org.apache.tapestry5.runtime;
  2. import org.apache.tapestry5.ComponentResourcesCommon;
  3. import org.apache.tapestry5.EventContext;
  4. /**
  5. * 事件类,可能产生于程序逻辑,也可能由于客户端交互请求产生(比如客户端产生的一个GET或者POST请求)。
  6. *
  7. * @see ComponentResourcesCommon#triggerEvent(String, Object[], org.apache.tapestry5.ComponentEventCallback)
  8. * @see org.apache.tapestry5.ComponentEventCallback
  9. */
  10. public interface ComponentEvent extends Event
  11. {
  12. /**
  13. * 如果该事件与提供的信息匹配则返回 true
  14. *
  15. * @param eventType      事件类型(大小写不敏感)
  16. * @param componentId    待匹配的组件(大小写敏感),可为空
  17. * @param parameterCount context中数据的最小个数
  18. * @return 如果匹配返回true
  19. */
  20. boolean matches(String eventType, String componentId, int parameterCount);
  21. /**
  22. * 将一个特定Context值转换为另一个指定类型的值。Context是一个对象数组,一般来说是一个字符串数字,被作为路径之外的信息编码进URL中。
  23. *
  24. * @param index           context值的位置
  25. * @param desiredTypeName 期望的数据类型
  26. * @return 转换后的值(如果期望的数据类型是基本数据类型,则为其包装类)
  27. */
  28. Object coerceContext(int index, String desiredTypeName);
  29. /**
  30. * 以数组的形式返回其持有的{@link org.apache.tapestry5.EventContext} (可能为空) .
  31. */
  32. Object[] getContext();
  33. /**
  34. * 返回其持有的event context.
  35. */
  36. EventContext getEventContext();
  37. }

这个接口继承了Event接口

[java] view plaincopy
  1. package org.apache.tapestry5.runtime;
  2. /**
  3. * 与事件处理相关的核心方法。事件通过调用用户代码并截获其返回数据去获取用户代码的数据。返回数据如果非空,将会传递给{@link
  4. * org.apache.tapestry5.ComponentEventCallback}。子接口 {@link ComponentEvent} 继承了这个接口,并提供了访问context,
  5. * 或者其它一些与事件相关的数据, 而且能和其它一些运行时的数据匹配,找到事件处理函数。
  6. */
  7. public interface Event
  8. {
  9. /**
  10. * 如果事件已经失效( 表示某个事件处理函数返回的值已经被框架接受,该事件不应再度传播).
  11. *
  12. * @return 如果不应再度传播这个事件,则返回true
  13. */
  14. boolean isAborted();
  15. /**
  16. * 用于定位什么样的事件,被什么样的组件的方法接受了(用于产生异常发生时的报告).
  17. *
  18. * @param methodDescription 描述一个函数的位置信息(如:文件名, 方法名 和行号)
  19. */
  20. void setMethodDescription(String methodDescription);
  21. /**
  22. * 保存某个事件的结果。保存一个非空的数据可能会使事件失效 (决定权在
  23. * {@link org.apache.tapestry5.ComponentEventCallback}).
  24. *
  25. * @param result 某个方法被调用后返回的结果
  26. * @return 如果结果使事件失效返回true
  27. */
  28. boolean storeResult(Object result);
  29. }

上述代码,我已将注释全部翻译为中文,看过相信会对事件处理的大体架构有所了解,下面我给出一个简单的页面Java类,以及框架为其生成的事件分派函数。

[java] view plaincopy
  1. @SuppressWarnings("unused")
  2. public class TUpdater {
  3. @InjectComponent
  4. private Zone zone;
  5. @InjectComponent
  6. private Zone zone2;
  7. @OnEvent(component = "tzoneupdater")
  8. Object onActionFromZone(String context, JSONObject json) {
  9. return zone.getBody();
  10. }
  11. @OnEvent(component = "tzoneupdater")
  12. Object onActionFromZone(String context) {
  13. return zone.getBody();
  14. }
  15. @OnEvent(component = "tactionupdater")
  16. Object onActionFromActionlink(String context, String msg1, String msg2) {
  17. return true;
  18. }
  19. @OnEvent(component = "tactionupdater")
  20. Object onActionFromActionlink(String context, JSONArray array) {
  21. return true;
  22. }
  23. @OnEvent(component = "form", value = "submit")
  24. Object onActionFromSubmit(Object... objects) {
  25. return zone2.getBody();
  26. }
  27. @OnEvent(component = "tjsonrequester", value = TDojoEventConstants.JSONRequest)
  28. public Object onJSONRequest(JSONObject param) {
  29. return new JSONArray(param.get("name"), param.get("age"));
  30. }
  31. }

为了突出事件处理,所有与实践无关的代码均被我删除,只保留了事件处理函数。OnEvent注释信息有一个默认的value值,为action。另外,代码中用到了一些JSONObject的数据,这些是我在应用中扩展Tapestry后支持的,不会影响到阅读,但不能直接模仿着写。有关这方面的支持,后续我会放到我的一个dojo与tapestry集成的开源项目中去http://tdojo.sourceforge.net/ ,但是目前还处于试验阶段。敬请关注!

生成的事件分派函数如下:

[java] view plaincopy
  1. public boolean dispatchComponentEvent(ComponentEvent $1)
  2. {
  3. //变量$_  用于记录该组件是否处理了这个事件
  4. if ($1.isAborted()) return $_;
  5. try
  6. {
  7. if ($1.matches("action", "tactionupdater", 3))
  8. {
  9. $_ = true;
  10. $1.setMethodDescription("/*包名被省略*/.TUpdater.onActionFromActionlink(java.lang.String, java.lang.String, java.lang.String) (at TUpdater.java:55)");
  11. onActionFromActionlink((java.lang.String)$1.coerceContext(0, "java.lang.String"), (java.lang.String)$1.coerceContext(1, "java.lang.String"), (java.lang.String)$1.coerceContext(2, "java.lang.String"));
  12. }
  13. if ($1.matches("action", "tactionupdater", 2))
  14. {
  15. $_ = true;
  16. $1.setMethodDescription("/*包名被省略*/.TUpdater.onActionFromActionlink(java.lang.String, org.apache.tapestry5.json.JSONArray) (at TUpdater.java:60)");
  17. onActionFromActionlink((java.lang.String)$1.coerceContext(0, "java.lang.String"), (org.apache.tapestry5.json.JSONArray)$1.coerceContext(1, "org.apache.tapestry5.json.JSONArray"));
  18. }
  19. if ($1.matches("submit", "form", -1))
  20. {
  21. $_ = true;
  22. $1.setMethodDescription("/*包名被省略*/.TUpdater.onActionFromSubmit(java.lang.Object[]) (at TUpdater.java:65)");
  23. //($w) 表示如果需要则将原始类型进行包装
  24. if ($1.storeResult(($w)onActionFromSubmit($1.getContext()))) return true;
  25. }
  26. if ($1.matches("action", "tzoneupdater", 2))
  27. {
  28. $_ = true;
  29. $1.setMethodDescription("/*包名被省略*/.TUpdater.onActionFromZone(java.lang.String, org.apache.tapestry5.json.JSONObject) (at TUpdater.java:43)");
  30. if ($1.storeResult(($w)onActionFromZone((java.lang.String)$1.coerceContext(0, "java.lang.String"), (org.apache.tapestry5.json.JSONObject)$1.coerceContext(1, "org.apache.tapestry5.json.JSONObject")))) return true;
  31. }
  32. if ($1.matches("action", "tzoneupdater", 1))
  33. {
  34. $_ = true;
  35. $1.setMethodDescription("/*包名被省略*/.TUpdater.onActionFromZone(java.lang.String) (at TUpdater.java:49)");
  36. if ($1.storeResult(($w)onActionFromZone((java.lang.String)$1.coerceContext(0, "java.lang.String")))) return true;
  37. }
  38. if ($1.matches("JSONRequest", "tjsonrequester", 1))
  39. {
  40. $_ = true;
  41. $1.setMethodDescription("/*包名被省略*/.TUpdater.onJSONRequest(org.apache.tapestry5.json.JSONObject) (at TUpdater.java:84)");
  42. if ($1.storeResult(($w) onJSONRequest((org.apache.tapestry5.json.JSONObject)$1.coerceContext(0, "org.apache.tapestry5.json.JSONObject")))) return true;
  43. }
  44. }
  45. catch (RuntimeException ex) { throw ex; }
  46. catch (Exception ex) { throw new RuntimeException(ex); }
  47. }

2. 流程分析

从生成的代码可以清晰的看出,组件会逐个与事件处理函数进行匹配。如果某个函数与当前事件匹配,则将数据转换为其期望的类型,作为参数传递给事件响应函数。如果事件处理函数有返回值,框架则会保存下来做处理,一旦事件失效,则不会继续往下查找是否有还存在匹配的处理函数。对于没有返回值的处理函数,则可能有多个函数会被调用到。

Tapestry默认的会按照参context中的参数个数搜索响应的事件处理函数(并不是严格的个数匹配)。有一点是你必须注意的,对于那些没有返回参数的响应函数,或者是返回值为空时,如果你不希望这个函数执行完后不会再有其它响应函数被调用的话,那么你最好是返回一个true来显示的告诉框架,事件该失效了。否则,在存在多个同一事件的响应函数时,可能会产生让你不知所谓的异常。

比如在我给出的例子中,如果响应组件tactionupdater的带有3个参数的事件响应函数不返回true,那么带有两个参数的事件响应函数也会被调用到,但是由于它们之间参数类型区别明显,所以会报出一个类型不能转换的异常(除了符合JSON格式的String,其它的都不能转换为JSON数据,而带两个参数的处理函数第二个参数是JSONArray类型的数据)。

这是因为Tapestry在匹配响应函数,采用了一种模糊的事件匹配算法。只要context中的参数个数大于事件处理函数的个数,同时产生事件的组件id与事件监听的组件id匹配或者监听id为空,那么Tapestry就会认为该处理函数监听了这个事件。

具体匹配策略如下代码所示:

[java] view plaincopy
  1. public boolean matches(String eventType, String componentId, int parameterCount)
  2. {
  3. return this.eventType.equalsIgnoreCase(
  4. eventType) && context.getCount() >= parameterCount && (originatingComponentId.equalsIgnoreCase(
  5. componentId) || componentId.equals(""));
  6. }

3. 动态参数解决方法

从上述流程分析可以看出,静态指定参数会受到搜索策略的一些影响,当一个事件参数变化频繁时,事件处理机制并不一定能找到你所期望的处理函数。这种情况下,你很可能希望能在一个函数里面接受这些事件怎么处理。

一个办法的将事件处理函数写成动态参数的形式:

[java] view plaincopy
  1. @OnEvent
  2. Object onAction(Object... context){
  3. }

这是一个比较简单,也比较容易理解的形式,但是这种方法有一个弊端,传入的参数很可能是未经转型的。也就意味值你需要自己转换所有的数据类型。

另一种更好的方式是使用一个特殊的参数类型org.apache.tapestry5.EventContext,使用方式如下:

[java] view plaincopy
  1. @OnEvent
  2. Object onAction(EventContext context) {
  3. }

这是一个Tapstry支持的参数机制,通过EventContext可以自动的完成类型转换,其接口如下:

[java] view plaincopy
  1. package org.apache.tapestry5;
  2. /**
  3. * 一组将传递给事件处理函数的参数,如果需要可以支持参数类型的转换。
  4. *
  5. * @see org.apache.tapestry5.ioc.services.TypeCoercer
  6. * @see org.apache.tapestry5.ValueEncoder
  7. */
  8. public interface EventContext
  9. {
  10. /**
  11. * 返回可以提取出的参数个数.
  12. */
  13. int getCount();
  14. /**
  15. *使用特定的参数类型获取指定位置上的参数.
  16. *
  17. * @param desiredType 期望的类型
  18. * @param index       指定待提取的参数位置
  19. * @return 转换后的数据
  20. * @throws 如果数组越界,或者不能找到相应的数据转换器,则会抛出RuntimeException
  21. */
  22. <T> T get(Class<T> desiredType, int index);
  23. }

但是这一方法也有一定的局限,因为它也仅能解决你知道参数类型时的转型问题,而不能自动的做到像函数重载一样的参数类型匹配。原则上还是以参数个数为主要参考值。(当然也不是所有类型都可以互换的,要Tapestry的转型机制支持才行,必要的情况下你可以扩展这个机制)

所以说,Tapestry事件处理机制,尚且只能支持到参数个数变化的程度,并不能根据事件处理函数的重载性质搜索到严格匹配的事件处理函数。这一点是你在使用Tapestry5时必须注意的。如果说你必须使用context参数传递变化的个数和类型的参数,最好传递一些额外的信息来辅助自己做数据类型的转换。不过要注意的是,虽然参数化设计在对象设计里面是很优美的一种方法,但在Web开发里面,却有很多你不得不妥协的地方,比如url长度对于传递参数大小的限制,比如无状态的交互特性等,都使得你无法传递复杂的参数。

一个总的原则,使用context传递都最好是少量简单类型的数据。如果有复杂的数据参数传递,最好使用Tapestry的其它机制,比如form的提交,或者直接调用目标页面的某个方法传递参数等。

以上是Tapestry事件处理机制的一个大体概述,有事件处理相关的另一个机制就是事件处理函数返回结果的处理机制。

这部分内容请见我的另一篇博文《Tapestry5 事件处理函数返回结果处理策略 》。

Tapestry5 事件分派机制相关推荐

  1. android触摸事件分发,Android 事件分发机制

    Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过.今天下定决心了解一下,以便后面自己定制 View 效果.Android 触摸事件有三个基本类型:ACTION_DOW ...

  2. epoll边缘触发_epoll事件通知机制详解,水平触发和边沿触发的区别

    看到网上有不少讨论epoll,但大多不够详细准确,以前面试有被问到这个问题.不去更深入的了解,只能停留在知其然而不知其所以然.于是,把epoll手册翻译一遍,更深入理解和掌握epoll事件处理相关知识 ...

  3. 安卓自定义View进阶-事件分发机制详解

    原文地址:http://www.gcssloop.com/customview/dispatch-touchevent-source Android 事件分发机制详解,在上一篇文章 事件分发机制原理  ...

  4. boost log 能不能循环覆盖_前端基础进阶(十四):深入核心,详解事件循环机制...

    Event Loop JavaScript的学习零散而庞杂,很多时候我们学到了一些东西,但是却没办法感受到进步!甚至过了不久,就把学到的东西给忘了.为了解决自己的这个困扰,在学习的过程中,我一直在试图 ...

  5. Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  6. js中如何得到循环中的点击的这个id_Js篇面试题9请说一下Js中的事件循环机制

    虽互不曾谋面,但希望能和您成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 标星公众号(ID:itclanCoder) 如果不知道如 ...

  7. View事件分发机制(源码 API27)

    1.什么是事件分发机制 当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程 用户触摸屏幕会产生一个事件流(ACTION_DO ...

  8. Android事件分发机制详解

    2019独角兽企业重金招聘Python工程师标准>>> 之前在学习Android事件方法机制的时候,看过不少文章,但是大部分都讲的不是很清楚,我自己理解的也是云里雾里,也尝试过阅读源 ...

  9. htmljavascript 事件触发机制

    html 事件触发机制 <!DOCTYPE html> <html> <head><meta charset="UTF-8">< ...

  10. Android 利用源码调试 详解TouchEvent 事件分发机制

    1.如果有触摸事件,首先会调用到Activity 的dispatchTouchEvent 方法. public boolean dispatchTouchEvent(MotionEvent ev) { ...

最新文章

  1. Java项目:精品酒店管理系统(java+SSM+mysql+maven+tomcat)
  2. Red hat linux 下配置Java环境(jdk)
  3. 创建自己的共用js库
  4. 我与微软的不解之缘 - 我的Insider Dev Tour 2019讲师之旅
  5. 作者:Anjaneyulu Passala, 男,印度理工学院计算机科学与工程学院博士,印孚瑟斯技术有限公司主任研究科学家。...
  6. leetcode 删除排序数组中的重复项
  7. 计算两个NSDate是否处于同一天
  8. java单链表基本操作 一,Java_实现单链表-基本操作
  9. redis的简易安装
  10. MYSQL索引和权限管理
  11. 免费远程桌面连接工具
  12. HPSocket C++控制台版DEMO
  13. SQL中NOW() 函数
  14. 中科院信工所经验_信工所六室面试经历
  15. 波浪数,51nod1788,根号分治+Meet in the Middle
  16. DSPE-PEG-MAL,474922-22-0,DSPE-PEG-Maleimide
  17. python图像增强之随机翻转或随机旋转
  18. linux终端下如何分屏,ubuntu terminal 终端分屏
  19. CMD 常用命令总结
  20. 输入一个字符串,将字符串中的大写字母改成小写字母,小写字母不变,其他字符忽略,然后输出转换之后的结果。

热门文章

  1. cad帧数测试软件,怎样让cad运行速度更快_cad如何设置运行更流畅
  2. AIX 系统默认ftp
  3. 比较完整的熊猫烧香解决方案
  4. Java SSM 商户管理系统 客户管理 库存管理 销售报表 项目源码
  5. 轻量级过程改进之项目启动
  6. vulkan sdk 下载地址
  7. minecraftjava版光追_我的世界:光追技术终于开始测试?没想到网易版已更新狐狸生物?...
  8. 软件dfmea_fmea软件|失效模式与效应分析(fmea)下载 v03.01.13.0中文版 - 121下载站
  9. 作为移动开发程序员,腾讯3轮面试都问了Android事件分发,分享PDF高清版
  10. 《深入理解JAVA虚拟机》周志明 第三版 - 第三章 垃圾收集器与内存分配策略