事件过滤器的设计
本章将事件过滤器的设计与使用.第一个需要考虑的问题是你需要的是过滤器还是插件.决定使用过滤器后,本章会关注使用过滤器对于改善程序结构和流程的现实意义,以及更深层次的讨论,包括表单处理,确认以及安全问题.
事件过滤器,还是插件?
当你刚开始使用Mach-II的时候,很难分清楚是需要一个事件过滤器还是需要一个插件.你需要问自己以下几个关于你的目标的问题,从而哪个更适合用来解决你需要解决的问题:
l 你需要在每个单一事件开始(或结束)时执行某些操作吗?
l 你需要在每个单一事件句柄开始(或结束)时执行某些操作吗?
l 你需要在每个单一页面编译开始(或结束)时执行某些操作吗?
如果你对其中任何一个问题回答”是”的话,你可能需要一个插件.
l 你需要对提供给确定事件的数据执行某些操作吗?
l 你需要对确定事件返回的数据执行某些操作吗?
l 你需要有条件地中止确定事件吗?
如果你对其中任何一个问题回答”是”的话,你可能需要一个事件过滤器.
如果你对以上问题均回答”不” 的话,请继续看本章其余部分以及下一章关于插件的内容,再自行决定.
对事件过滤器的剖析
事件过滤器是提供filterEvent()
方法的一种组件, filterEvent()方法可被以下三种变量调用:
1. 当前事件
2. 当前事件上下文
3. (可选项)由来自过滤器调用部分(在mach-ii.xml文件中)的< parameter>标签的name/value对构成的结构体
filterEvent
返回true,表示继续事件句柄的处理;返回false,表示结束事件句柄的处理(接着处理队列里的下一个事件,除非在过滤器里使用arguments.eventContext.clearEventQueue()方法清除事件队列.通常,当过滤器返回false,它在此之前还是会通告新的事件(如果它清除了事件队列,它就必须在此后通告一个新的对象).
事件过滤器在需要的时候可以定义configure()方法来实现初始化—这将被框架自动调用.和Mach-II框架的其他部件一样,事件过滤器也被储存在application域,所以它们的实例变量是有效的application域变量.
事件过滤器组件最简单的例子:
<cfcomponent extends="MachII.framework.EventFilter">
<cffunction name="configure" returntype="void" access="public" output="false">
<!--- perform any initialization --->
</cffunction>
<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
<cfargument name="event" type="MachII.framework.Event" required="yes" />
<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
<cfargument name="paramArgs" type="struct" required="yes" />
<!--- return false if you need to abort the current event --->
<cfreturn true /> <!--- indicate success --->
</cffunction>
</cfcomponent>
事件过滤器被声明在mach-ii.xml
的<event-filters> .. </event-filters>
标签里:
<event-filter name="filterName" type="Path.To.YourFilter" />
filterName
过滤器在mach-ii.xml
文件的名称,Path.To.YourFilter
是YourFilter.cfc
完整的组件名称.作为可选项,你可以定义参数给过滤器:
<event-filter name="filterName" type="Path.To.YourFilter">
<parameters>
<parameter name="param1" value="value1" />
<parameter name="param2" value="value2" />
</parameters>
</event-filter>
这样可以为param1
和 param2
提供默认参数(分别是value1
和 value2
). 可以使用getParameter()方法来访问它们.
事件句柄访问过滤器,如下:
<filter name="filterName" />
可以为标签添加参数:
<filter name="filterName">
<parameter name="param1" value="newValue1" />
<parameter name="param3" value="value3" />
</filter>
这样使filter()方法被当前事件调用,当前事件上下文和paramArgs结构体包含两个键:值为newValue1的param1以及值为value3的param3.这样做的意图是使得在过滤器声明时定义的参数值可以被此方式传递的参数值重写.通常需要这样来管理它:
<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
<cfargument name="event" type="MachII.framework.Event" required="yes" />
<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
<cfargument name="paramArgs" type="struct" required="yes" />
<cfset var param1 = getParameter("param1") />
<cfset var param2 = getParameter("param2") />
<cfset var param3 = getParameter("param3") />
<!--- other var declarations --->
<cfif structKeyExists(paramArgs,"param1")>
<cfset param1 = paramArgs.param1 />
</cfif>
<cfif structKeyExists(paramArgs,"param2")>
<cfset param2 = paramArgs.param2 />
</cfif>
<cfif structKeyExists(paramArgs,"param3")>
<cfset param3 = paramArgs.param3 />
</cfif>
<!--- perform filter processing --->
<!--- return false if you need to abort the current event --->
<cfreturn true /> <!--- indicate success --->
</cffunction>
事件过滤器既可以访问当前事件也可以访问事件上下文,因此它可以通过操作事件上下文来影响程序逻辑流.例如,当过滤器决定中止当前事件(通过返回false的方式),它一般需要通知另一个事件给框架执行,它也可以决定清除任何等待执行的事件.按惯例,事件过滤器需要一个决定通告哪个事件的参数,和一个决定是否清除事件队列的参数:
<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
<cfargument name="event" type="MachII.framework.Event" required="yes" />
<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
<cfargument name="paramArgs" type="struct" required="yes" />
<cfset var invalidEvent = getParameter("invalidEvent") />
<cfset var clearEventQueue = getParameter("clearEventQueue") />
<!--- other var declarations --->
<cfif structKeyExists(paramArgs,"invalidEvent")>
<cfset invalidEvent = paramArgs.invalidEvent />
</cfif>
<cfif structKeyExists(paramArgs,"clearEventQueue")>
<cfset clearEventQueue = paramArgs.clearEventQueue />
</cfif>
<cfif someCondition>
<!--- note: clearEventQueue parameter is really a string --->
<cfif clearEventQueue is "true">
<cfset arguments.eventContext.clearEventQueue() />
</cfif>
<!--- pass current event's arguments into the new event: --->
<cfset arguments.eventContext.announceEvent(invalidEvent,arguments.event.getArgs()) />
<cfreturn false />
</cfif>
<cfreturn true />
</cffunction>
数据确认
当用户数据进入你的程序时,不管是URL域还是变量域的变量,你一般都需要一定程度的数据确认.这有时和核对一样简单,明确变量被提供的话;有时却很复杂.
Mach-II框架提供了RequiredFieldsFilter,
允许你与特定的URL或表单域变量列表进行核对,并因为参数中有一个或多个不符合要求通告特定事件,来中止当前事件.这个过滤器该有两个参数:
l requiredFields –用逗号隔开的需要被核对的字段名列表
l invalidEvent – 当字段不符时要通告的事件
如果字段不符,被通告的事件除了有与当前事件一样的两个参数外,还有两个附加参数:
- message – 出错信息(英文的)
- missingFields – 逗号隔开的不符字段列表
对于更加复杂的表单,你需要用一个bean来封装表单内容(使用<event-bean>
创建bean对象),提供对数据对象的validate()方法,返回一个布尔值.然后你可以使用ValidateFormObject过滤器(**********)来调用这个方法,并处理任何确认失败情况.
这个过滤器带有3个参数:
l formObjectName - validate() 的事件对象目标
l invalidEvent - validate()返回false时通告的事件
l clearEventQueue – 可选项, 返回布尔值, 显示是否在通告invalidEvent之前清除事件队列
如果validate()
返回false
,过滤器会在清除事件队列(这是可选操作)后也返回false,然后通告特定事件,使用一个附加变量:
l formObjectName
– 传入过滤器的名称
这样特定事件就可以应付确认失败的状况,后者一般会涉及对数据对象的额外调用,而附加的过滤器调用正好可以管理好这些交互作用.
你可以为ValidateFormObject
定义默认参数:
<event-filter name="barValidator" type="MachII.filters.ValidateFormObject">
<parameters>
<parameter name="formObjectName" value="bar" />
<parameter name="invalidEvent" value="formHasInvalidBar" />
</parameters>
</event-filter>
<event-filter name="fooValidator" type="MachII.filters.ValidateFormObject">
<parameters>
<parameter name="formObjectName" value="foo" />
<parameter name="invalidEvent" value="formHasInvalidFoo" />
<parameter name="clearEventQueue" value="true" />
</parameters>
</event-filter>
现在,你无需每次指定参数就可以使用这些过滤器,或者重写默认条件:
<event-bean name="bar" type="my.model.bar" />
<filter name="barValidator" />
...
<event-bean name="foo" type="my.model.foo" />
<filter name="fooValidator">
<parameter name="invalidEvent" value="warnAboutBadFoo" />
<parameter name="clearEventQueue" value="false" />
</filter>
安全问题
当用户授权需要在确定事件执行,一个事件过滤器往往可以事半功倍.Mahc-II框架提供PermissionsFilter
来支持简单的基于许可证的认证核对.这个过滤器用来核实当前用户是否具有所有必要的许可证(由逗号隔开的列表指定),如果没有,它将通告指定的事件(在可选的清除事件队列的操作之后).可以查看源码来得到更多信息.
你可以简单地扩展PermissionsFilter组件来实现你自己的安全保护,重写getUserPermissions()方法,并返回逗号隔开的用户许可证列表.如果你不想要默认包含的许可证核对行为,或者你的用户许可证比列表更加复杂,你也可以重写validatePermissions()来完成你的核对工作.
如果你的安全系统比简单许可证更加复杂,有必要写自己的安全事件过滤器.