这一章主要讲mach-ii.xml的事件句柄部分.如果你遵循了总体设计考量,你可能在开始设计事件句柄时,就已经设计了(并可能执行了)你的事务模型组件和HTML页面.在设计页面方面,简单的交互图表需要足够详细,才能满足事件句柄的实际需要.
每个的用户交互对应一个事件:每个链接,每个表单的提交都是一个事件,包括用户请求的第一个网页调用的默认事件.因为公有的事件名称会是程序的可视(用户可以直接看到的)部分,所以只得为此挑选易读懂的事件名称,使用verbNoum(动宾)结构.使用这种大小写混合格式可以增加可读性.在Mach-II里事件名称对大小写不敏感,所以即使用户在URL里错打成verbnoun也没关系.另一个可选方案视用verb.noun(这对哪些使用过Fusebox里的circuit.fuseaction的用户来说会很熟悉.
用户交互表单所关联的事件构成了你的应用程序的第一层.间接调用的基础是将组件间的控制流抽象成事件模型,所以你会发现大量的事件被你的程序逻辑所关联.例如,当你触发一个带有判断流(比如对或错的逻辑)的事件,你往往需要为各个判断结果创建事件—参考下面的程序中立事件一节.即使流程是直线性的,也经常需要从后来申请的流程(例如嵌入界面)中分离出原始用户事件.事件句柄一般应该简短和明了—再提醒一次,这个做法是高内聚性和高耦合性要求推荐的.命名事件来反应内部功能,例如,sectionVerNoun或者section.ver.noun.
还句话说,没必要的情况下不要分解事件句柄—如果你有一队连在一起的listener方法调用,应该使用<event-arg>将listener调用拴在一起,而不是分割成一系列伪私有事件(artificial private events).可以使用<event-arg>来将请求域数据移动到事件对象:
<notify listener="myListener" method="methodOne" resultKey="request.result" />
<event-arg name="oneArg" variable="request.result" />
<notify listener="myListener" method="methodTwo" resultKey="request.result" />
公有事件句柄和私有事件句柄 Public & Private Event Handlers
用户交互产生的事件只是Mach-II程序的一个部分,它们是用户产生的公共事件.用于这些事件的事件句柄需要用access="public"
来声明.所有其他事件句柄--那些程序自动生成的触发事件—而被声明为access="private"
.用于默认事件的事件句柄应该定义为access="public"
.
声明事件句柄为私有可以确保它们不被恶意的URL调用.这译为着你只需要考虑用户输入数据在公共事件句柄中的验证,然后在提交控制权给内部(私有)事件句柄前装配好事件数据.使用事件过滤器来验证和操作事件数据—见事件过滤器的设计一节.
大多数面向对象的程序设计语言都推荐先声明公共实体后声明私有实体.这对声明事件句柄一样有益,并按首字母排列易于在较长的mach-ii.xml文件里查找.
程序中立事件
如果你的listener需要通告事件,应该选择适合这个listener的事件名称而不是整个程序,也就是说,使用程序中立事件.你可以使用event-mapping标签来映射listener的事件到那些经过程序验证的事件.这同样是内聚和耦合概念所促成的,可以提高复用性和可维护性::使用程序中立事件降低listener和程序之间的耦合性,使listener可以复用到其他程序.
例如,一个登录listener需要通告loginSucceeded或者loginFailed
(或者更多信息如 loginBadPassword
, loginNoSuchUser
等),然后事件句柄需要映射它们到程序需要的东西上:
<event-handler event="adminLogin">
<event-mapping event="loginSucceeded" mapping="showAdmin" />
<event-mapping event="loginFailed" mapping="showLogin" />
<notify listener="adminListener" method="login" />
</event-handler>
在这个例子里, adminListener
里的login()方法使用announceEvent()
通告程序中立事件loginSucceeded
或loginFailed
,而这些事件映射将其翻译了,所以放置在事件队列的分别是showAdmin
或者showLogin.
事件映射只用在<event-mapping>命令内部有效(因此需要在它将影响的listener通告前使用<event-mapping>命令).
共享句柄的执行通道
在开发程序的时候,经常看到相类似的代码出现在一些事件句柄里.例如,编辑和创建选项经常有类似获取/载入数据的流程(你可以在通讯录(ContactManager)的例子里看到).创建复杂的布局(layout)是一个另一个选择:在事件句柄底部创建通用的代码.在这些例子里,考虑将常见操作转移到独立的事件句柄,仅从原始事件句柄通告新的常用事件.
即使是不同的代码,你也经常可以从中抽象出变量,并转化成通用代码.为解决这个问题,你的原始句柄将做定量的调整,也就是说,使用<event-arg>来为不同代码定义的值,然后通告新的事件.
分解和链接事件句柄
如果你将一个事件句柄分离成多个私有事件句柄,你需要考虑它们彼此之间如何交流:
<event-handler event="processForm" access="public">
<announce event="handleFormData" />
<announce event="displayResult" />
</event-handler>
因为handleFormData
和displayResult
都被processForm事件句柄通告,它们是新的事件对象,都包含来自processForm事件对象的事件变量的一个复本.然而,在这个事件句柄里,对handleFormData的改动不会反应在displayResult事件句柄上—它们是不同的事件.这就是说两个事件句柄在请求域的交流比理想的要少.
需要什么才能”将事件联结在一起—还句话说就是在以上代码隐性(
implicit)的序列转化为显性(
explicit)
的序列.为什么这些序列是隐性的?因为它一类于事件队列—先执行handleFormData
然后再执行displayResult
.一个提高显性的方法是为handleFormData
提供事件句柄来显性地通告队列的下一个事件,并传递事件变量以确保任何改动的传递.如果handleformData只被一个事件句柄通告,你可以这样做:
<event-handler event="processForm" access="public">
<announce event="handleFormData" />
</event-handler>
<event-handler event="handleFormData" access="private">
.. handle the form data ..
<announce event="displayResult" />
</event-handler>
实际项目中, handleFormData一般会通告多个事件句柄,而displayResult也常常不是下一个被通知的事件.因此,你需要动态选择下一个被通告的事件.有很多方法,不过最简单的可能算是使用<event-arg>来储存下一个事件的名称,然后事件过滤器来通告它—这个技术有时被称为延伸(continuation):
<event-handler event="processForm" access="public">
<event-arg name="continuationEvent" value="displayResult" />
<announce event="handleFormData" />
</event-handler>
<event-handler event="handleFormData" access="private">
.. handle the form data ..
<filter name="continuation" />
</event-handler>
延伸过滤器只通告被continuationEvent
确定的事件:
<cfset arguments.eventContext.announceEvent(
arguments.event.getArg("continuationEvent"),
arguments.event.getArgs() ) />
异常触发
当一个异常出现在过滤器或者listener方法里的时候,框架捕捉这个异常,创建一个包含异常的具体信息的MachII.util.Exception
对象,并通告异常事件,这些是mach-ii.xml文件的<properties>部分定义的.你也可以使用
handleException()
插件来提供自定义处理异常.
你也可以使用<event-mapping>
在异常出现的时候触发特定的事件.
<event-handler event="someEvent" access="public">
<event-mapping event="defaultException" mapping="someFilterException" />
<filter name="someFilter" />
<event-mapping event="defaultException"
maping="someListenerException" />
<notify listener="someListener" method="someMethod" />
</event-handler>
在这个例子中,假定defaultException
被exceptionEvent变量在mach-ii.xml文件的<properties>部分定义了.如果这个异常发生在someFilter,
框架将通告事件someFilterException.
如果异常发生在someListener.someMethod()
,框架将通告someListenerException.
如果一个异常出现时没有映射被激活,将通告defaultException事件.一个映射的生命周期是从它被声明的点到事件句柄的结尾.