单页系统前端MVC设计 – ActionMapping过程

上一篇讲到通过对外暴露LocationManager组件,提供接口进行redirect等操作,可以形成一个Request对象交给Controller(即调用Controllerprocess函数)。

本篇主要讲述Controller如何处理一个Request,包括通过Request查找到对应的Action,并准备好上下文对象,交进而进入到Action的执行流程中。

ActionMapping和Controller

Request对象

类似服务器端的MVC框架,Request对象将提供对一次请求中所能接触到的所有内容的封装,其会包含以下内容:

  • url、path、query属性,对应window.location的href、pathname和search属性,表示整个请求资源符或者其中的一部分。
  • getParameter(key)函数,获取query中某个键对应的值。
  • getSession(key)函数,获取Session或者Session中某个键对应的值。
  • getAppContext(key)函数,获取AppContext或者AppContext中某个键对应的值。

Session对象及AppContext对象

在服务器端,通常一个用户会形成一次会话,该用户的多次请求可以共享一份会话,用于在请求之间传递一些值。而Application则是整个应用系统生命周期中始终保留的值。

对应前端的MVC框架中,在此将一个Action的多次使用映射为一个用户的多次请求,因此每个Action会有一个自己的Session区域,该Action的多次执行可以共享该区域中的变量。

AppContext则随着整个系统的开始而开始,结束而结束,在整个系统的生命周期中始终存在。

按此方式进行划分,Session适用于存放一些有固定的作用范围但又需要在多次访问中共享的数据,如:

  • 列表页面的搜索表单的条件,保证下次回到该列表页面时,依旧会按原有的搜索条件筛选数据。
  • 新增、修改表单的数据,保证用户在未提交表单的情况下离开的话,下次回来还可以继续填写。

AppContext用于存放多个Action都需要使用的一些数据,如:

  • 各种常量,以及相关的本地化使用的资源字典。
  • 当前的用户的信息、角色信息、权限资料等。
  • 全局配置,如分页中的每页显示数量,可用于所有列表页面。

SessionAppContext对象都会提供getsetclear三个函数,具体作用完全可以通过名称来确定,不再一一赘述。需要注意的是,如果不指定任何参数调用get,可以将SessionAppContext中管理的所有数据变为一个键值对象返回。

ActionMapping组件

ActionMapping是整个框架动作过程中非常关键的一环,在ActionMapping之前和之后可以说是两个完全不同的世界。在ActionMapping之前,整个流程都处在框架的控制之中,完全没有任何地业务逻辑;而在ActionMapping后,业务逻辑开始进入,真正的用户交互才开始进行。

组件作用

ActionMapping组件用于通过一个Request对象,查找到该对象对应的Action类。

由于随着系统规模的扩大,Action也会逐渐增多,因此ActionMapping需要通过最适合地方式管理映射关系,使得整个项目在实现过程中,可以采用最简化的配置完成Request->Action的映射关系。

组件设计

ActionMapping组件将提供3个层面的映射功能,根据优先级从高到底,依次为:

  1. Region映射,该方式会将“一类”Request映射到某一个Region中,Region本身不指向一个具体的Action,而一另一组配置的集合体。也就是说,Region仅实现对配置的划分。

    比如,以下请求都是与用户有关的,且其对应的资源定位串有着相同点:

    • /user/list:用户列表。
    • /user/create:新建用户。
    • /user/modify:修改用户。

    可以通过观察发现,以上的资源定位符都是以/user/开始,因此将/user这一串作为一个Region,在该Region下再详细配置listcreatemodify三个具体的配置项。

  2. UrlMatch映射,UrlMatch是最简单的映射方式,他可以工作在全局ActionMapping中,也可以工作在某个具体的Region下。

    UrlMatch通过简单的request.path为键,从配置表中获取对应的Action类的路径并返回来实现。

  3. DefaultRule映射,DefaultRule映射是为了减少配置而采用的“基于约定”的配置方式,如通过约定所有的资源定位符采用/{namespace}/{action}的形式,即/user/list这样一个资源定位符会被映射到user.List这个Action中。

    DefaultRule的基础上,通过约定Action的命名规则,可以省去相当多的配置项,简化项目的编码。

    同时DefaultRule也是ActionMapping对外开放的一个配置项,外部可以通过重写这个规则,实现不同的默认映射规则。

接口设计

ActionMapping组件对外暴露以下接口:

  • findAction(request)函数,通过给定Request对象,找到对应的Action类,如无法找到则返回null。需要注意的是,该函数只会找到对应的类,而不会对其进行实例化,实例化依旧交由Controller完成,这是因为ActionMapping组件仅维护映射的关系,但并不关心Action本身的构造函数、依赖对象,所以仅仅返回一个Action的类定义。
  • defaultRule,该配置项是一个函数,可以重写该函数来实现默认的映射逻辑。

实现思路

ActionMapping相当于一个字典,其实现相对来说较为简单,首先其内部维护一个RegionConfig的键值对:

var regions = {};  
var mapping = {};  

当调用findAction函数时,则通过分析Request对象的path属性,分解出RegionAction,并通过以下逻辑进行查找:

  1. 查找regions[region][action],如果得到结果则返回,否则进入下一步。
  2. 查找mapping[path],如果得到结果则返回,否则进入下一步。
  3. 调用defaultRule(request)进行查找,如果得到结果则返回,否则返回null

Controller组件的流程

Controller组件,或许称为一个对象更为合适。其用于协调框架的入口和Action之间的交互,同时负责创建请求的上下文环境。

Controller组件的流程如下:

  1. 通过process(request)的调用,进入流程。
  2. 调用currentAction属性指向的Action对象的leave函数,尝试离开正在执行的Action
    • 如果无法成功离开该Action(比如用户正在编辑,并在提示用户后确定不想离开),则需要退回URL的变化。
    • 如果成功离开了正在执行的Action,则进入下面流程。
  3. 调用ActionMapping组件的findAction函数,获取Action类。
  4. 实例化该Action类,得到Action对象,并将对象通过currentAction变量保留引用。
  5. 查看该Action中的Metadata,根据指定的配置查看当前用户的权限,如果用户没有进入该Action的权限,则给用户相应的反馈,停止流程。
  6. Action准备足够的执行上下文,包括RequestSessionAppContext。同时将前面三个对象通过全局变量的方式暴露出来,以保证部分需要使用这些变量但又无法访问到Action对象的组件可以获取其引用。
  7. 调用Actionexecute函数,进入Action的生命周期。

Controller流程