技术开发 频道

Spring MVC向导控制器

【IT168技术文档】
概述

    假设某一个系统在用户注册模块中需要区别一般用户和高级用户,一般用户只要提供最简单的信息,通过一个小表单就可以搞掂了。但对于需要注册为高级用户的客户来说,论坛希望他们提供详细的注册信息,除了用户名、密码、Email这些最简单的信息外,还需要提供住址、电话以及兴趣爱好之类的调查信息。通过一张大表单让注册者一次性填写所有这些信息并不是一个好主意,大部分潜在的用户当看到这样面目狰狞的“超级表单”后都会毫不犹豫的放弃注册。这时通过一个向导式的表单让用户分步填写注册信息将是明智的方案,虽然需要填写的信息量不变,但心理学的经验告诉我们,用户会在第一感觉简单的心理暗示下慢慢进入我们设下的“麦田圈套”中。

    高级用户注册所需填写的信息分解到3个表单中,并以向导方式分步完成:

1) 填写用户名、密码、Email等一般的信息;
2) 填写地址、电话等联系的信息;
3) 填写用户兴趣爱好的调查信息。 

    在其它MVC框架中开发向导式的表单并非易事,因为你需要考虑表单前进、后退、中途退出,表单分步骤校验,数据维护等诸多的问题。幸运的是,在Spring MVC中,你不必躬身考虑这种底层工作流程的细节,AbstractWizardFormController已经编制好了向导表单的工作流程并将那些需要你确定的步骤开放出来,你只需要通过扩展现成的AbstractWizardFormController通过很少的工作,一个功能强大的向导表单就大功告成了。

我们打算通过以下页面流程完成高级用户注册的操作:

图 1 注册高级用户向导页面流程

创建注册高级用户的向导控制器

    我们构建一个向导控制器,它必须继承AbstractWizardFormController类,FullUserRegisterController负责为注册高级用户提供基本的向导控制器,其代码如下所示:
代码清单 1 FullUserRegisterController:向导控制器 

package com.baobaotao.web.user;

import org.springframework.web.servlet.mvc.AbstractWizardFormController;
public class FullUserRegisterController extends AbstractWizardFormController {
private String cancelView; ① 点击取消后转向的视图(逻辑视图名)
private String successView; ② 向导最终处理成功后转向的成功页面
private BbtForum bbtForum;
public void setBbtForum(BbtForum bbtForum) {
this.bbtForum = bbtForum;
}
③ 负责处理最后表单提交的动作
protected ModelAndView processFinish(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
FullUser fullUser
= (FullUser) command;
bbtForum.registerFullUser(fullUser);
-1转向welcome.jsp页面
return new ModelAndView(getSuccessView(), "fullUser", fullUser);
}
④负责处理取消的动作
protected ModelAndView processCancel(HttpServletRequest request, HttpServletResponse
response, Object command, BindException errors) throws Exception {
return new ModelAndView(getCancelView());④-1转向main.jsp页面
}
//省略get/setter
}

    AbstractWizardFormController唯一必须实现的方法是processFinish()方法,在用户完成整个向导的提交后,这个方法被调用执行。在FullUserRegisterController中,processFinish()方法将FullUser对象传递给BbtForum业务对象,保存高级用户的注册信息,然后转向到注册成功后的欢迎页面。你可以有选择地实现processCancel()方法,该访问负责处理取消的动作。向导链中的页面都提供一个可以让用户中途退出的“取消”功能,它可以增强了向导的灵活性。在④处,我们指定当用户点击“取消”从向导中途退出时,转向到main.jsp页面中。

     AbstractWizardFormController并没有提供“取消视图”和“成功视图”的配置属性,这不能不说是一个遗憾。所以我们在FullUserRegisterController中①和②处分别定义了用于取消页面和成功页面的逻辑视图名。

   读者可能会发出这样的疑问:表单对象和向导链上的页面视图究竟在哪里定义呢?你也许已经猜测到,它们应该出现在配置文件中。我们马上看一下FullUserRegisterController的具体配置:

<bean name="/fullRegisterUser.html" class="com.baobaotao.web.user.FullUserRegisterController">
<property name="bbtForum" ref="bbtForum" /> ① 业务对象
<property name="commandClass" value="com.baobaotao.domain.FullUser"/> ②表单对象类
<property name="pages" value="fullRegister,relation,favorite"/> ③向导链页面逻辑名,用逗号隔开
<property name="cancelView" value="main"/> ④取消后转向的视图
<property name="successView" value="welcome"/> ⑤向导处理成功后转向的视图
</bean>

    在前面的实例中,我们都是通过在控制器构造函数中通过调用setCommandClass()方法指定表单对象(命令对象),这里我们通过配置进行指定,如②所示。

    ③处通过pages属性定义了构成向导链的页面视图,pages属性是一个String[],你不但可以通过<list><value></value></list>的方式进行配置,也可以通过逗号分隔的字符串的方式进行配置,后者显然要更简洁一些。“fullRegister,relation,favorite”传达了两个信息:

1)向导链由三个视图组成,分别是fullRegister、relation和favorite;
2)向导链的视图顺序是fullRegister->relation->favorite。

  在④和⑤处分别定义了取消和提交表单转向的视图,这样向导控制器就知道哪些页面构成了表单,当进行取消和提交表单操作时需要转向到哪些页面。至此,形如图 1所描述的整体向导流程就搭建完成了。
虽然我们已经知道了整个向导的页面组成,但是组成向导链的页面需要做哪些配合工作,以便让向导正确串接起来呢?

将向导页面串接起来
 
    任何向导控制器的第一个页面都是pages属性指定的第一个视图(可以通过覆盖方法改变)。在高级用户注册向导中,第一个显示的页面是即fullRegister视图(通过视图解析器解析,你可以简单地将其看为fullRegister.jsp)。

    当AbstractWizardFormController接收到请求时,它根据咨询getTargetPage()方法判断要转向到哪个目标视图。getTargetPage()方法返回一个整数值,它代表向导页面的索引值,以0为基数,也就是说0代表fullRegister,1代表relation,以此类推。

    getTargetPage()方法的缺省实现是根据请求中的一个特定参数来确定的,这个参数以“_target”开头,以数字结尾。getTargetPage()方法去掉“_target”前缀得到剩下的数字,以它作为目标页面的索引值。例如,如果请求中的一个参数名为“_target1”,那么用户将被带到索引为1的relation页面。

    了解getTargetPage()的工作原理有助于我们在HTML页面中构造下一步和上一步的按钮。例如对于relation视图页面(对应的页面索引为1),要在这个页面创建下一步和上一步按钮,你要做的就是创建提交按钮,并以_targetX进行命名,如下所示:
 
代码清单 2 relation.jsp:填写用户联系信息页面

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>宝宝淘论坛用户注册</title>
</head>
<body>
<form:form>
地 址:<form:input path="address" /><br>
电 话:<form:password path="telephone" /><br>
<input type="submit" name="_target0" value="上一步" />①导向fullRegister视图页面
<input type="submit" name="_target2" value="下一步" />①导向favorite视图页面
</form:form>
</body>
</html>

    当上一步按钮被按下时,名为_target0的参数被放到请求中,连同其它表单数据发送到FullUserRegisterController控制器中,控制器的getTargetPage()方法处理这个_target0参数,得到目标页面的索引是0,这样就可以将用户带到fullRegister的页面中。如果用户点击下一步按钮提交表单,_target2的参数被放到请求中,这时用户将被带来favorite页面。
完成和取消向导

    我们现在知道了如何让向导前进和后退,如果希望中途从向导中退出,或在最后一步提交整个向导,应该如何去做呢?FullUserRegisterController提供了processCancel()和processFinish()方法,它们分别处理取消向导和提交整个向导的工作,现在最重要的问题是如何触发这两个操作?和前进后退的触发机制类似,向导控制器通过两个特殊的提交参数进行控制:当请求中包含“_cancel”参数时,调用processCancel()方法取消向导,当请求中包含“_finish”参数时,调用processFinish()完成向导。

    按照上一步和下一步相似的方式,我们只需要将提交按钮命名为“_cancel”和“_finish”就可以完成取消和完成向导的操作了。来看一下favorite页面是如何做到这点的:

<form:form>

<input type="submit" name="_target1" value="上一步" />
<input type="submit" name="_finish" value="确 定" /> ①点击该提交按钮,完成向导
<input type="submit" name="_cancel" value="取 消" />②点击该提交按钮,取消向导
</form:form>
当点击“确定”按钮时,“_finish”被添加到请求参数列表中,FullUserRegisterController执行processFinish()方法,调用业务对象对FullUser进行业务操作,并导向到welcome页面中。当点击“取消”按钮时,“_cancel”被加入到请求参数列表中,processCancel()被调用,取消向导并导向到main页面中。
分步骤校验表单数据

    和表单控制器一样,你可以为向导控制器配置一个校验器,让其负责对表单对象进行数据校验,不过两者的校验流程存在区别。这是因为,如果对表单对象校验太早,由于有些表单对象属性值要在后续步骤中才能收集到,这将导致过早校验的问题。反之,如果校验时间点太晚,提供表单对象属性的页面距离当前已经跨越了多个页面,当发生错误时将无法正确导向到调整的页面,这无疑会影响交互性造成不好的用户体验。所以必须分步骤进行有针对性的校验,而非一次性校验,即控制器每次仅对当步提交的数据进行校验。

    在我们高级用户注册的向导中,第一步骤要求用户填写用户名、密码、Email的信息,当用户点击下一步到relation页面时,就必须对这三个属性值进行校验,如果发生错误就应该马上返回到提供这三个属性值的fullRegister页面中,以便用户及时纠正。
在每次页面跳转时,validatePage()方法会被调用,用以校验表单对象的数据合法性。该方法缺省实现是空的,我们可以通过覆盖该方法做出自己的判断。在校验时需要结合page页面索引号进行分步骤校验。

    首先我们编写一个校验器,它为每一步骤提供了相应的校验方法:

代码清单 3 FullUserValidator
package com.baobaotao.domain;

public class FullUserValidator implements Validator {

public boolean supports(Class clazz) {
return clazz.equals(FullUser.class);
}
public void validate(Object command, Errors errors) {①完全校验

}
public void validateStep1(Object command,Errors errors){ ②第一步的校验

}
public void validateStep2(Object command,Errors errors){ ③第二步的校验

}
}

    FullUserValidator为第一,二步向导分别提供了校验,而validate()用于向导最后提交时的校验。在FullUserRegisterController在validatePage()方法中根据page属性所标识的步骤执行相关的校验:

代码清单 4 FullUserRegisterController# validatePage()
protected void validatePage(Object command, Errors errors, int page, boolean finish) {
FullUserValidator validator = (FullUserValidator)getValidator();
if (page == 0) { ①第一步提交的校验
validator.validateStep1(command, errors);
}else if (page == 1) {②第二步提交的校验
validator.validateStep2(command, errors);
}else if (finish) { ③向导最后提交的校验
validator.validate(command, errors);
}
}

    当向导页面提交时,page表示提交的页面索引,我们可以据此实施特定步骤的校验。当向导最后提交时finish属性为true,这时我们就进行整体表单对象校验。

最后我们要做的是在配置文件中将FullUserValidator装配到FullUserRegisterController中,如下所示:

<bean name="/fullRegisterUser.html" class="com.baobaotao.web.user.FullUserRegisterController">
<property name="bbtForum" ref="bbtForum" />
<property name="commandClass" value="com.baobaotao.domain.FullUser"/>
<property name="pages" value="fullRegister,relation,favorite"/>
<property name="cancelView" value="main"/>
<property name="successView" value="welcome"/>
<property name="validator"> ①注入校验器
<bean class="com.baobaotao.domain.FullUserValidator" />
</property>
</bean>

     在①处注入校验器,这样validatePage()方法就可以通过getValidator()获取校验器并实施分步校验了。

小结

    在Struts、WebWork等MVC框架中开发具有向导功能的模块往往是比较复杂的,因为向导的各项处理动作和前后的衔接都必须由开发者负责,从框架中得不到任何的帮助。而在Spring MVC中开发具有向导功能的模块是非常容易的,因为Spring MVC已经通过AbstractWizardFormController“预编制”好了向导的流程,开发者仅需要按照特定的规则实现特定的方法,使用特定的URL请求参数就可以开发出一个功能强大的向导模块了。
0
相关文章