MachII HowTo系列教程的译文: Mach-II 如何开发Listener

 

作者:  Ben Edwards 

 

 

1II体系下的Listener

   在实际开发过程中软件体系通常被加工为组件component和连接器connector)的集合。组件是系统的功能元素。比方说,一个购物车,一个通讯录,或者一个数据库都可以是软件体系的组件。连接器是用于组件间通讯的协议,包括方法的调用,SQL查询和HTTP请求。系统所选择的体系决定了组件和连接器的词汇表(vocabulary),一种定义如何组合二者的约束的集合。

 

   对定义体系中的组件和连接器外露界面的这一事项来说,很重要的风格特征(metrics)就是系统的内聚和耦合。内聚是组件用途单一性的度量标准。组件的内聚性越高,它的功能就越专注,复用所承担的假设就越少。

 

   耦合性可用来度量组件间依赖程度。组件间依赖程度越低(它们的联系越松散),它就越独立、复用性也越好。最大化的内聚和最小化的耦合是灵活、易维护体系结构的两大特点。

 

   基于事件,II(implicit invocation××)是具有高内聚和低耦合的精工体系的范例。因此,它被软件工程普遍接受。II体系的例子随处可见,事实上包括所有现代的操作系统、完整的开发环境和数据库管理系统等。

 

   II系统由事件驱动。事件在系统需要做某事时被触发——比如像对引入请求的响应。事件在不同类型的实现中采取不同形式;对于Mach-II,事件是属性值里包含运行该事件所需上下文信息的对象(类似HTTP请求携带它所有表单和查询语句变量的方式)。

 

    当一个事件被通告,系统就开始为该事件查找listener组件。Listener符合我们刚刚已经讨论过的组件创建标准——它们是系统的功能模块。请求作为listener的组件在配置时段里被(XML文件)注册,进而在运行时由确定的事件通告。当事件被触发,所有注册的该事件关联的listener经由动态确定(dynamically-determined)方法调用的手段,传递给该事件。通过这种方式,功能被间接地调用。事件通告(notify)listener的这种处理方式被称作事件宣告(event announcement)。

 

 

2。面向对象的框架下的listener

 

Listener在Mach-II中有三个主要的任务:

     ×Listener组件包含Mach-II应用程序的事务逻辑;

     ×Listener由与其由注册关系的事件通报;

     ×Listener可以宣告(announce)新的事件。

 

在用于ColdFusion的Mach-II中,框架组件,包括listener,被以ColdFusion组件(CFCs)的形式创建。所有为Mach-II应用程序开发的listener皆继承于Listener.cfc(MachII.framework.Listener),后者包含在Mach-II框架内核代码中。

 

Listener具有一些重要的功能,和三个对开发人员创建自己的listener相当重要的方法:

     ×announceEvent(string eventName.[struct eventArgs])——用于向框架通告一个新事件;

     ×configure()——在初始化组件后调用,来重写进而确定构造器(configuration)逻辑;

     ×getParameter( string paramName )——用于访问在XML配置文件中制定的配置参数。

 

在CFML组件浏览器查看Mach-II.framework.Listener组件,或许对你理解listener有所帮助。进入http://localhost:8500/cfide/componentutils/componentdoc.cfm,然后点击MachII.framework包。点击Listener组件查看该组件的具体信息。

 

3。创建Mach-II应用程序

 

在我们开始创建listener之前,让我们先创建一个Mach-II应用程序来测试即将创建的listener。我们需要使用从www.mach-ii.com下载来的MachAppSkeleton文件创建我们的应用程序。

 

MachAppSkeleton文件是创建新的Mach-II程序的模板,其中包含MachAppSkeleton-Readme.txt这个指导文件。我们将摘取其中的要点来创建程序和测试listener。

 

我们将要创建的程序可以起名为MachII_HowTo_Listeners。将MachAppSkeleton文件夹里面的内容全部拷到你的CF wwwroot下的名为MachII_HowTo_Listeners的文件夹里。

 

新的文件夹看上去像一下所示:

 

 

 

 

下一步打开程序根目录下的Application.cfm文件设置<cfapplication>标签的name属性值为MachII_HowTo_Listeners

 

接着,打开mach-ii.xml。该XML文件的的第一部分定义程序的参数。设置参数applicationRoot为你的程序文件夹的路径,相对于网站根目录并以/开头。对于这个程序,applicationRoot的值应为MachII_HowTo_Listeners

 

然后,设置参数defaultEvent值为showLoginForm。这是学定我们的程序的默认事件,它不需要用event-handler声明。

 

目前为止,mach-ii.xml配置文件的<properties>部分看上去像以下所示:

 

<!--- PROPERTIES --->

<properties>

    <property name="applicationRoot" value="/MachII_HowTo_Listeners" />

    <property name="defaultEvent" value="showLoginForm" />

    <property name="eventParameter" value="event" />

    <property name="parameterPrecedence" value="form" />

    <property name="maxEvents" value="10" />

    <property name="exceptionEvent" value="exceptionEvent" />

</properties>

 

接下来注册两个<page-view>一个是为loginForm.cfm另一个是为loginWelcome.cfmloginForm.cfm由一个提交username和password作为login事件(我们尚未为其声明event-handler)的form组成。loginWelcome.cfm则由简单的问候语和一个通告showLoginForm事件的链接构成。这两个页面的代码可以在文章结尾得到。

 

目前为止,mach-ii.xml配置文件的<page-views>部分看上去像以下所示:

<!-- PAGE-VIEWS -->

<page-views>

    <page-view name="loginForm" page="/views/loginForm.cfm" />

    <page-view name="loginWelcome" page="/views/loginWelcome.cfm" />

    <page-view name="exception" page="/views/exception.cfm" />

</page-views>

 

 

最后,为我们的默认事件showLoginForm<event-handler>。联络公共事件触发器(event-handler

public),使得它可以由网络请求(web-request)通告。当下的这个event-handler唯一需要做的事就是显示loginForm这个page-view

 

目前为止mach-ii.xml配置文件的<event-handler>部分看上去像以下所示

<!-- EVENT-HANDLERS -->

<event-handlers>

<event-handler event="showLoginForm" access="public">

<view-page name="loginForm" />

</event-handler>

<event-handler event="exceptionEvent" access="private">

<view-page name="exception" />

</event-handler>

</event-handlers>

 

现在我们可以打开http://localhost:8500/MachII_HowTo_Listeners,并可以看到登录表单。因为网址中没有包含用以确定某个事件的参数,框架将触发默认事件(showLoginForm),即显示loginForm.cfm页。提交这个登录表单不会产生任何反应,因为我们还没有为这个登录事件确定event-handler

 

5。创建新的listener组件

首先,定义一个名为LoginListenerCF组件(LoginListener.cfc),来做基于用户名和密码的登录权限鉴别。文件创建在程序的model目录下(推荐,但非必须)。同时,确保LoginListener扩展(extend)了MachII.framework.Listener,这样它就可以继承(inheritslistener组件原型的所有功能了。

 

<cfcomponent displayname=”LoginListener”

extends=”MachII.framework.Listener”

hint=”Authenticates a user’s login credentials based on username and

password.”>

</cfcomponent>

 

 

,接着,我们来的定义configure函数,目前先不定义它的函数体。在定义方法时,我们其实已经重写了Listener.cfc组件原型,因此,必须保留函数的argument和各种属性(如accessreturntype等等)。

 

在学习到configure()函数更多内容前,我们的代码会像这样:

<cffunction name=”configure” access=”public” returntype=”void”>

<!--- DO NOTHING --->

</cffunction>

 

注意,不要为Mach-II的组件(如Listener)定义我们自己的init函数。它们已经在框架核心文件中被定义好了,且不可以重写。换句话说,定义在组件的Configure()方法是专门为自定义组件重写设计的。这些configure()函数在紧跟在框架初始化(initialization)阶段的框架配置(configuration)阶段被调用。

 

注册的listener实例在框架初始化的时候被创建和初始化。只有到框架被重新初始化的时候,同样的listener实例才会被维护和使用。关于初始化和配置阶段的具体信息超出了本文范畴,目前关键要明白,configure()函数是在所有的事件被触发前,声明listener需要运行的配置代码的。

 

最初的这几步,是我们每次创建新的listener的时候都要重复的。先是,声明listener组件,让它扩展MachII.framework.Listener,然后定义configure()函数来支持某些配置上的逻辑。

 

 

6。定义listener的函数

 

现在,我们来创建事务逻辑。现在我们要定义的LoginListener是负责查看用户名和密码,并鉴别用户权限的。

 

建立一个名为“attemptLogin”的函数来支持鉴定逻辑。注意,函数的access必须为declare,这样在能是方法被Mach-II框架调用。定义返回值为“void”(就是说,该函数将不返回任何值)。

 

 

你会问,如果attemptLogin()函数不返回值,我们该如何知道登录的尝试是否成功呢?要知道,Mach-II框架是有事件驱动的,listener可以通告事件来执行任务。所以,LoginListener会以公告事件的方式,来宣布登录成功与否。

 

对于事件的通讯,我们需要需要让attemptLogin()函数可以接受叫做“event”的变量。这个event是必须的,且类型为MachII.framework.Event

 

目前的attemptLogin()函数看上去,像这样:

 

<cffunction name=”attemptLogin” access=”public” returntype=”void”>

<cfargument name=”event” type=”MachII.framework.Event” required=”true” />

<!--- TODO: GET USER INFO --->

<!--- TODO: AUTHENTICATE USER INFO --->

<!--- TODO: ANNOUNCE SUCCESS/FAILURE EVENT --->

</cffunction>

 

 

在被框架调用时,所有用来做登录尝试的用户信息将被封装事件对象当中。取得这个事件里的用户名和密码,要使用事件的getArg()函数:

 

<cffunction name=”attemptLogin” access=”public” returntype=”void”>

<cfargument name=”event” type=”MachII.framework.Event” required=”true” />

<!--- GET USER INFO --->

<cfset var username = arguments.event.getArg(‘username’) />

<cfset var password = arguments.event.getArg(‘password’) />

<!--- TODO: AUTHENTICATE USER INFO --->

<!--- TODO: ANNOUNCE SUCCESS/FAILURE EVENT --->

</cffunction>

 

一旦得到用户信息,我们就需要判断它们是否有效。由于潜在的判断方法有很多种(比如databaseLDAP等等),我们将把这个功能分配给另一个叫做“isLoginValid”的函数来实现。这种做法对保持attemptLogin()函数的内聚性很有帮助!

 

isLoginValid函数的访问域设为public,返回值设为boolean。它还需要变量usernamepassword(类型都是string)。现在姑且设置函数的返回值为true,以后我们会回过头来完善它的。

 

<cffunction name=”isLoginValid” access=”public” returntype=”boolean”>

<cfargument name=”username” type=”string” required=”true” />

<cfargument name=”password” type=”string” required=”true” />

<cfreturn true />

</cffunction>

 

 

7。在listener中通告事件。

有了isLoginValid(),我们就可以完成attemptLogin()了。将我们从事件中得到的用户名和密码值传递给isLoginValid(),看看是否有效。如果有效,我们将通告“loginSuccess”事件,否则通告“loginFailure”事件。

 

为了通告事件,我们需要使用Listener.cfc原型中定义的announceEvent()函数。该函数将取得两个变量,第一个是eventName,是必要条件且必须是字符类型的。它定义了要通告的事件的名称。第二个是eventArg,是可选条件,不过需要的话,必须为结构体类型。它定义了将封装到新事件内的变量。

 

完成后的attemptLogin()如下:

 

<cffunction name=”attemptLogin” access=”public” returntype=”void”>

<cfargument name=”event” type=”MachII.framework.Event” required=”true” />

<!--- GET USER INFO --->

<cfset var username = arguments.event.getArg(‘username’) />

<cfset var password = arguments.event.getArg(‘password’) />

<!--- AUTHENTICATE USER INFO --->

<!--- ANNOUNCE SUCCESS/FAILURE EVENT --->

<cfif isLoginValid(username, password)>

<cfset announceEvent(‘loginSuccess’) />

<cfelse>

<cfset announceEvent(‘loginFailure’) />

</cfif>

</cffunction>

 

 

如果需要将用户登录信息放入loginSuccessloginFailure事件的话,可以使用下面的代码:

 

<cfset announceEvent(‘loginSuccess’, arguments.event.getArgs()) />

 

这个代码可以将传递给attemptLogin()的所有变量,存入新的事件。

 

 

8.注册和配置listener

 

我们现在已经得到开发好了的LoginListener,需要把它注册到mach-ii.xml配置文件中。

<listeners>部分的代码如下:

 

<!-- LISTENERS -->

<listeners>

<listener name="LoginListener"

type="MachII_HowTo_Listeners.model.LoginListener">

<invoker type="MachII.framework.invokers.CFCInvoker_Event" />

</listener>

</listeners>

 

Listener定义里的名称属性可以是任意值,它只是用来调用XML里的listener的标志符。Type属性则必须是通向该组件的完整路径。

 

必须注意的是那个<invoker>标签,invoker type值用来确定事件信息由何种方式传递给被调用的listener的。以后将进一步研究invoker,而现在要确保invoker type为“MachII.framework.invokers.CFCInvoker_Event

 

我们还将为新的listener函数分扩展提供event-handler,另外还有为登录事件(包括loginSuccessloginFailure

 

 

登录事件句柄(event-handler)会通报LoginListenerattemptLogin()函数。LoginSeccess事件句柄将简单地显示loginWelcomeloginFailure事件句柄将通过showLoginForm事件再次显示loginForm。这两个句柄的访问域值设置为private

 

 

将这些事件句柄加入<event-handlers>部分:

 

<!-- Notify the listener to validate login. -->

<event-handler event=”login” access=”public”>

<notify listener=”LoginListener” method=”attemptLogin” />

</event-handler>

 

<!-- On login success, show a welcome page. -->

<event-handler event=”loginSuccess” access=”private”>

<view-page name=”loginWelcome” />

</event-handler>

 

<!-- On login failure, show the login form. -->

<event-handler event=”loginFailure” access=”private”>

<announce event=”showLoginForm” />

</event-handler>

 

 

此时,我们需要回到LoginListener<listener>元素上,再添加几个配置参数。这些参数在XML文件中确定下来,可以由listenergetParameter()函数访问。

 

 

在实际应用中,用户名和密码一般会通过数据库和LDAP服务来检验。在我们这个例子里,我们将添加两个参数:validUsernamevalidPassword。显然,它们表示有效的用户信息,值可以随便设置,在本例,我们设置为“implicit”和“invocation”。

 

 

将参数加入<listener><parameters>里:

 

<listener name="LoginListener"

type="MachII_HowTo_Listeners.model.LoginListener">

<invoker type="MachII.framework.invokers.CFCInvoker_Event" />

<parameters>

<parameter name="validUsername" value="implicit" />

<parameter name="validPassword" value="invocation" />

</parameters>

</listener>

 

 

现在我们要完成isValidLogin()函数,让它来核对用户提交的信息和配置文件中设置的validUsernamevalidPassword。用继承于listener原型的getParament函数,访问配置参数:

 

<cffunction name=”isLoginValid” access=”public” returntype=”boolean”>

<cfargument name=”username” type=”string” required=”true” />

<cfargument name=”password” type=”string” required=”true” />

<!--- AUTHENTICATE AGAINST PARAMETERS IN CONFIG XML --->

<cfif arguments.username EQ getParameter(‘validUsername’) AND

arguments.password EQ getParameter(‘validPassword’)>

<cfreturn true />

<cfelse>

<cfreturn false />

</cfif>

</cffunction>

 

 

使用参数除了可以确定有效用户信息外,还可能用来确定联系LDAP服务器和数据库的方法。

 

访问http://localhost:8500/MachII_HowTo_Listeners,提交表单,表单对Mach-II框架的请求将触发带有变量usernamepassword、名为login的事件。登录事件句柄将通告LoginListener,并向它的attemptLogin方法传递该事件。通过事件的信息,listener会执行事务逻辑,并通报新的事件——loginSuccess或者loginFailure

 

 

 

10.调用listener

在我们提交登录表单给Macf-II框架的时候,后者做了很多工作来确保登录事件已经传递到我们的LoginListener上。其中的关键组件,叫做invoker

 

Invoker是用来动态调用listener方法的。在注册listenerXML配置文件的同时,我们也为listener确定了一个invoker。框架通过这个invoker调用listener的方法来触发事件。

 

listener一样,所有Mach-IIinvoker都是被创建成CFC格式。自定义的invoker都继承了包含在Mach-II框架核心代码包里的ListenerInvocker.cfc原型(MachII.framework.ListenerInvoker)

MachII.framework.invokers包里含有两种类型的invoker——CFCInvoker_Event and

CFCInvoker_EventArgs

 

 

名为CFCInvoker_Eventinvokerlistener方法传递实体事件。因此,该方式调用的listener的方法需要类型为MachII.framework.Event、名为“event”的变量。

 

<invoker type="MachII.framework.invokers.CFCInvoker_Event" />

<cffunction name=”attemptLogin” access=”public” returntype=”void”>

<cfargument name=”event” type=”MachII.framework.Event” required=”true” />

. . .

</cffunction>

 

 

 

 

名为CFCInvoker_EventArgsinvoker需要将事件的变量与listener的方法需要的变量一一对应,并逐个传递。比方说,一个事件带有变量username‘password,而被调用的listener的函数中存在名为“username”和“password”的两个变量,这样事件变量将以同名称传递给被调用的函数。

 

 

<invoker type="MachII.framework.invokers.CFCInvoker_EventArgs" />

 

<cffunction name=”attemptLogin” access=”public” returntype=”void”>

<cfargument name=”username” type=”string” required=”true” />

<cfargument name=”password” type=”string” required=”true” />

. . .

</cffunction>

 

 

 

 

<event-handler>标签里的<notify>元素指示框架去使用listenerinvoker调用具体的方法。属性Listener的值应是已经注册的listener的名称。属性mathed的值指定了listener里将被调用的方法名。可选属性resultKey(本例没有用到)可用来储存特定范围的返回值。

 

 

我们将LoginListenerinvoker类型设CFCInvoker_EventattemptLogin()函数接受名为“event”的单一变量。如果我们是用CFCInvoker_EventArgs类型的invoker,那么attemptLogin函数就必须含有两个变量——usernamepassword,来匹配事件中的同名变量。

 

 

11.总结与回顾。

 

ListenerMach-II中有三个主要的任务

     ×Listener组件包含Mach-II应用程序的事务逻辑

     ×Listener由与其由注册关系的事件通报

     ×Listener可以宣告announce新的事件。

 

在这篇文章中,我们已经学习了:

         ×怎样创建Mach-II应用程序;

         ×怎样创建Listener组件;

         ×如何在Mach-II配置文件中注册和配置listener

         ×怎样为listener制定invoker

         ×怎样创建event-handler,来使用listener履行事务逻辑;

         ×如何用listener通告event

 

 

12.术语和定义。

内聚性(cohesion):用来度量组件(和方法)用途专一程度;

耦合性(coupling):用来度量组件间独立程度;

         封装(encapsulation):使用类或组件来集中放置方法及其需要响应的变量;

         继承(inheritence):履行和某个类的特定关系,来继承指定的方法(××);

         父型(或者译为超类,supertype):将被其它类扩展的父类;

         重写(或者译为重构,overriding):一种沿用父类的方法名,但进一步确定功能的方法。

 

 

13.相关资源。

 

以下所有资源可以在www.mach-ii.com得到:

         ×本例的代码(MachII_HowTo_Listeners.zip);

         ×MachAppSkeleton代码;

×Mach-II配置指南;

×Ben Edwards的介绍II体系的文章;

×更多Mach-II教程。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值