控制器(Controller)由一些类组成,根据预先定义的配置选项处理用户请求。一个典型的用户请求如下:
php.MVC 控制器由两部分组成:前端控制器和控制器。当请求到来时,前端控制器负责安装应用程序,控制器则根据phpmvc-config.xml的配置属性处理请求。
图6显示了前端控制器的主要任务。
图6
用户请求被Main.php文件接收,这里,将设置一些初始化参数。前端控制器将执行以下任务:
- 定义应用程序路径:这将指定php.MVC类库以及Web应用程序的路径,如下:
$appServerRootDir = C:/WWW/phpmvc-base;
$moduleRootDir = C:/WWW/mycompany;
- 定义应用程序的ActionDispatcher:我们通常需要扩展框架ActionDispatcher来定义自己的Dispatcher类:
$actionDispatcher = MyActionDispatcher;
- 初始化应用程序类路径:为了将类和资源装入,前端控制器将导入预先定义的全局路径以及应用程序路径。我们可以在/WEB-INF/ModulePaths.php中设置路径,如下:
$appDirs = array();
...
$appDirs = WEB-INF/report_tpl;
$appDirs = WEB-INF/report_classes;
- 包含应用程序类:前端控制器将导入它所需要的类文件,我们也可以用/WEB-INF/prepend.php文件来有选择性地导入一些特殊的应用程序类文件。如下:
include_once ./WEB-INF/mytools/MyTools.php;
- 配置应用程序:前端控制器将为应用程序设置配置信息,比如我们先前定义的ActionDispatcher。
- 初始化控制器:前端控制器现在将创建一个应用服务器实例(ActionServer)。
- 导入配置信息:前端控制器现在将导入应用程序配置信息,假如phpmvc-config.xml从最后一次请求后被修改了,phpmvc-config.xml文件将被重新处理并将数据缓存到/WEB-INF/phpmvc-config.data中。
- 初始化HTTP请求:前端控制器现在将设置HTTP请求并添加请求属性。
- 调用应用程序控制器:前端控制器现在已经完成了准备工作,将会把处理权交给控制器。
控制器接收从前端控制器传来的请求,根据配置属性执行一系列操作。
图7描述了php.MVC控制器的任务。
图7
- 处理action路径:控制器将根据请求路径识别出关键字,选择一个action映射。比如请求路径为:
, action路径就为salesReport。
- 处理现场:根据需要为当前用户选择一个Locale
- 处理内容格式:根据需要设置内容格式,默认为text/html。
- 处理不缓存:根据需要设置不缓存头信息,默认为:
Pragma, No-cache
Cache-Control, no-cache
Expires, 1
- 处理预处理任务:可以在自定义的ActionServer子类中覆盖这个方法,执行一些指定的预处理任务。
- 处理Action映射:控制器将根据请求识别action映射,根据phpmvc-config.xml的相应节点生成action映射对象(ActionConfig),比如:。
- 处理角色:检查能执行这个action的所有必须的认证。
- 处理ActionForm:控制器将根据action映射找到相关联的ActionForm,form-bean就是由action的name属性指定,比如:
。
- 处理Populate:根据请求参数设置ActionForm实例的属性。
- 验证ActionForm:根据action的validate属性值如:,决定是否调用ActionForm的validate()方法。如果validate()返回False(验证失败),控制器将用action中input属性所指定的显示资源(模板)显示错误,比如:
...
validate = true
input = reportsIndex.tpl>
假如validate()返回True(验证通过),则将继续处理。处理Forward:控制器检查forward映射的URI是否正常,如果是,继续处理。
- 处理Include:控制器检查include映射的URI是否正常,如果是,继续处理。
- 处理Action创建:控制器将创建或获取Action实例来处理这个请求,这是用action的type属性来定义的,比如:
type = SalesReportAction
...
- 处理Action执行:控制器现在将调用Action类的execute()方法,比如SalesReportAction->execute(...)。在该方法内,我们将调用业务处理逻辑类。
- 处
理Action链:控制器将检查是否还有另外一个Action需要处理,在应用程序配置文件中,我们通过ActionChain能定义一系列
Action,为了定义ActionChain,需要为action节点的forward元素添加一个nextActionPath属性,比如:
type = SalesReportAction
...
name = salesReportSuccess
path = salesReport.tpl
nextActionPath = salesReport2/>
...
- forward元素的path属性是必须项,假如这个特殊的Action没有输出,我们可以设置path = 。
- 处理Action Forward:控制器将转发或重定向到指定的资源,一个forward请求在当前处理器中被处理。RequestProcessor只是把控制权交给ActionDispatcher,那里包含了指定的URI模板。比如:
name=forward_path
path=forwardRequest
redirect=false/>
- 重定向请求实际是发送给客户端浏览器一个标头响应,然后重定向到一个新的URL。在发送重定向标头信息时当前的处理将立即终止。
name=redirect_path
path=/MyApp/Main.php?do=newRequest
redirect=true/>
name=redirect_path
path=
redirect=true/>
假如没有其他的Action需要处理,控制器处理结束。
拦截过滤器
问题:
要再请求被处理之前,之后拦截并过滤操作一个请求和它的响应。
所谓请求的预处理和后处理,是指在请求的核心处理操作起之前、之后进行的操作。有些操作决定了处理过程是否还要继续,另一些则把输入输出数据流转化成合适进一步的形式。比如:
- 客户端是否有一个有效的会话?
- 请求的目录路径是否违反了限制条件?
- 系统是否支持客户端的浏览器类型?
- 客户端是用哪一种编码方式发送数据的?
- 请求数据流是否加密?是否压缩?
对于这些(以及其他很多)请求处理过程,一种常见的处理思路是用一长串的条件检查实现,通常会有嵌套的if/else语句来控制执行的流程。但是,既然在每个处理语句中执行的都是相似的操作,就会生成许多重复的代码。这样进行预处理和后处理导致代码质量脆弱、满是复制-粘贴份各国的程序,因为控制流程以及特定的处理操作和应用逻辑混在了一起。
而且这样的做法还会加大预处理/后处理组件与核心应用处理代码之间的耦合。
约束
- 要再多个请求之间实现集中,通用的处理过程,比如检查每个请求的数据编码方式、为每个请求留下日志信息或压缩输出响应。
- 要在预处理/后处理组件与请求处理核心服务之间实现松耦合,这样在加入/删除预处理/后处理操作就很容易,不会干扰核心服务。
- 要是预处理/后处理组建之间相互独立,每个组件都能自足存在,从而增进重用。
解决方案
使用拦截过滤器,作为一个可插拔式的过滤器,实现请求、响应的预处理和后处理。另有一个过滤器管理器,负责把各个处于松耦合关系的过滤器结合成一个链,并把控制一次委派给合适的过滤器。这样一来,不必改动现有代码就能够以各种方式加入、删除、合并这些过滤器。
策略
标准过滤器策略
定制过滤器策略
基本过滤器策略
模版过滤器策略
Web Service 消息处理策略
定制SOAP过滤器策略
JAX-RPC过滤器策略
效果
- 利用松耦合的处理起实现控制集中化
- 增进了重用
- 能灵活地通过声明配置
- 不便于信息共享
前端控制器
问题
你想给表现层的请求处理安排一个集中访问点。
系统需要一个集中的访问点来处理请求。如果没有集中访问点,那么多个请求之间公用控制代码就会在许多文件(比如视图文件)中重复出现。如果控制代码和视图创建代码混在一起,整个应用系统的模块化程度和内聚性都要下降。另外,如果控制代码在多处散放,也不便于代码维护,只要代码有一处改动,就需要改动多个文件。
约束
- 你想要避免重复控制逻辑
- 你想要对多个请求采取共通的处理逻辑
- 你想把系统处理代码和视图分割开
- 你想要把系统访问点集中在一起
解决方案
是用一个前端控制器,作为最初的接触点,用来处理所有相关请求。前端控制器集中了控制逻辑,避免了逻辑的重复,完成了主要的请求处理操作。
前端控制器通常会使用应用控制器,后者负责操作和视图管理(action and view management)
- 操作管理,也就是定位特定的操作(action)、并把控制权路由到该操作上,由他处理请求。
- 视图管理,则是指找到合适的视图,并分配到该视图上。虽然这个任务也可以放进前端控制器完成,但是把它独立出来,成为应用控制器的部分功能,能够增进模块化、可维护性和重用性。
设计手记:处理请求
处理请求需要两种操作:请求处理和视图处理。在请求处理过程中,表现层又必须进行几种操作:
- 协议处理和上下文转换
- 导航和路由
- 核心处理
- 分派
协议处理是指处理与特定协议相关的请求,上下文转换是指把与特定协议相关的状态转换成为更为通用的形式。一个例子是,从HttpServletRequest实例中获取参数,再复制到一个MyRequest对象中,从而能够在servlet环境之外独立应用。
导航和路由是指确定一个特定请求的路由,比如用那些对象进行请求的核心处理,用哪个视图显示结果。
核心处理也就是对请求的实际处理。
分派是指把控制权从应用系统的一个部分转交给另一个部分,比如从请求处理机制转交给视图处理组件。
策略
Servlet前端策略
JSP前端策略
命令加控制器策略
物理资源映射策略
逻辑资源映射策略
多路资源映射策略
“控制器中的分配器”策略
基类前端策略
过滤器控制器策略
效果
- 集中控制
- 提高系统可维护性
- 增进了重用
- 增进了开发团队中职责之间的区分
Context对象
问题
不想在与协议无关的环境上下文中使用针对特定协议的系统信息。
在请求和响应的整个生命周期中,一个应用系统通常要使用系统信息,比如请求、配置、安全数据等等。系统信息的获取方式与环境上下文(Context)有关。当负责业务应用的组建和服务必须使用一些处于他们的环境上下文之外是用一个特定协议的API,这就会把特定的接口和处理细节暴露给使用这个API的所有组件。这样,所有作为API的客户的组件也就和那个特定协议产生了紧耦合。
约束
- 组件和服务要访问系统信息。
- 要接触系统信息的协议细节与业务应用组件/服务之间的耦合。
- 只想在特定环境上下文中暴露于协议相关的API
解决方案
使用Context对象,按照协议无关的方式封装状态,然后在整个应用系统中使用这种封装后的对象。
策略
请求Context策略
请求Context表策略
请求Context POJO策略
请求Context验证策略[表单层次的验证、业务验证]
配置Context策略
JSTL配置策略
安全性Context策略
通用Context对象策略
Context对象工厂策略
Context对象自动复制策略
效果
- 提高了可重用性和可维护性
- 提高了可测试性
- 减少了对接口变化的限制
- 降低了性能
应用控制器
问题
要集中地、模块化敌进行操作管理和视图管理
约束
- 要重用操作控制和视图控制代码
- 要提高请求处理逻辑的可扩展性,比如要能够递增地在系统中加入新的用例功能。
- 要增进代码的模块化和可重用性,便于扩展系统,并且便于在Web容器之外测试请求处理代码的各个单独部分。
解决方案
用一个应用控制器把请求处理组件的获取和调用集中起来。
策略
命令处理器策略
视图处理器策略
应用控制器和Struts
转化处理器策略
导航和流程控制策略
消息处理策略
定制SOAP消息处理策略
JAX-RPC消息处理策略
效果
- 提高了模块化程度
- 提高了可重用性
- 提高了可扩展性