SimpleFormController以上两种主要能力,没有传授打通“经脉之法”。还好,BaseCommandController后继有人,AbstractFormController在BaseCommandController
的“武功基础上,发展了一套模板化的form处理流程。至此,从数据封装、验证、再到处理流程的模板化,整个规范化即建立成功
SimpleFormController专门面向单一的表单处理,而AbstractWizardFormController则提供多页面向导的交互能力。
要让SimpleFormController帮助简化Web请求处理工作,下面来看一下SimpleForm与生俱来的三种主要能力!
1、数据绑定
在web应用程序中使用数据绑定的最主要好处,我们再也不用自己通过request.getParameter(String)方法遍历获取每个请求参数,然后
根据需要转型为自己需要的类型了。SpringMVC提供的数据绑定功能帮助我们自动提取HttpServletResuqest中相应的参数,然后转型为
需要的对象类型。唯一要做的就是为数据绑定提供一个目标对象,在spring中称为Command对象,此后的Web处理逻辑直接同
数据绑定完成的Command对象打交道即可。
可以通过commandClass属性设置数据绑定的目标Command对象类型,如下:
<property name="commandClass">
<value>com.web.springMVC.model.UserModel</value>
</property>
<property name="commandName">
<value>command</value>
</property>或者直接在子类的构造方法中直接设定,如下:
public Login() {
//设置数据绑定的目标Command对象类型,这里直接实现了将请求参数绑定到对象上
setCommandClass(UserModel.class);
setCommandName("user");
//对象的名称setCommandName("user");
}
有了即将绑定到目标Command对象的数据来源之后,我们即可将这些数据根据Command对象各个域属性定义的类型进行数据转型,然后设置到Command对象上
从参数获取到参数转型并绑定到Command对象上,这整个流程最终以org.springframwork.web.bind.ServletRequestDataBinder的形式进行了封装
该类常见的使用的代码如下:
UserModel user=new UserModel();
ServlerRequestDataBinder dataBinder=new ServletRequestDataBinder(user);
dataBinder.bind(request);
Errors errors=binder.getErrors();
而之后,我们只需要使用数据绑定完成的Command对象进行后继处理就可以了。
2、Spring框架数据验证简介
Spring 数据验证框架核心类为org.springframework.validation.Validator和org.springframework.validation.Errors,Validator负责
具体的验证逻辑,而Errors负责承载验证过程中出现的错误信息,二者之间的纽带则是Validator接口定义的主要验证方法validato(target,errors)
把验证工作全部交给Validator的实现类去完成。下面是一个Validator的一个实现类:
public class UserValidator implements Validator{
//support(Class)方法定义,是为了进一步限定Validator实现类的职责,界定验证的范围
@Override
public boolean supports(Class clazz) {
return clazz.equals(UserModel.class);
}
@Override
public void validate(Object obj, Errors err) {
UserModel user=(UserModel)obj;
if(user.getPhone().length()<7){
user.setCreateTime(null);
err.reject("phoneErr", "电话号码位数要大于7");
}
else if(user.getAge()<=0){
user.setCreateTime(null);
err.reject("ageErr", "年龄要大于0");
}
}
}在SpringMVC中,Validator的执行以及后继的错误信息处理,将由BaseCommandController或者其子类接管,我们通常不需要操心这些
实现细节,唯一要做的就是通过相应的setter方法,注入需要使用的Validator实现类,如下使用IoC注入:
<!-- 这里直接实现了数据验证 -->
<property name="validator" ref="userValidator"></property>3、深入表单处理流程
在Web请求到达SimpleFormController之后,SimpleFormController将首先通过方法isFormSubmission(request)判明当前请求
是否为表单提交请求,isFormSubmission(request)的默认实现如下:
protected boolean isFormSubmission(HttpServletRequest request){
return "POST".equals(request.getMethod());
}整个表单处理流程将以isFormSubmission(request)的判定结果为标准,划分为”表单显示阶段“和”处理表单提交阶段“两个逻辑处理阶段。如果isFormSubmission(request)返回false,通常表示初次请求,这时我们需要为用户显示相应的交互表单,这也是表单显示阶段将要
做的事情。否则,认为用户已经在表单中编辑完数据,需要处理,SimpleFormController将启用"处理表单提交阶段"流程处理逻辑。
表单显示阶段流程分析
(1)创建或者获取表单对应的Command对象
此时formBackingObject()将默认通过反射实例化我们为SimpleFormController指定的Command对象实例。某些时候formBackingObject()
的默认行为不能符合当前场景要求,比如要更新一下实体的信息,那么在为用户显示表单的时候,就应该将要更新的实体的数据加载
到表单中,以便用户在原有数据的基础上进行更改,这时就要覆写formBackingObject(),改为自己管理Command对象的初始化
(2)初始化DataBinder
在执行请求参数到Command对象的数据绑定之前,初始化一个可用的DataBinder实例,就是ServletRequestDataBinder
有了可用的ServletRequestDataBinder之后,可以对其进行定制,比如添加自定义的PropertyEditor以支持特殊数据类型的数据绑定,或者排除
某些不想绑定的请求参数,这些定制行为通过initBinder()方法引入
(3)执行数据绑定
这一步只有在bindOnNewForm属性被设置为true的情况下才会触发执行,bindOnNewForm默认属性是false,所以,通常情况下是不会在显示表单之前
执行任何绑定操作的。但是如果针对SimpleFormController发起的初次请求中存在某些参数,并且我们想将他们绑定到已经创建的Command对象
,那么就可以在SimpleFormController的构造方法或者bean 定义上设置bindOnNewForm为true,剩下的事情就交给SimpleFormController去管了
(4)处理表单的显示
在最终显示表单页面之前,还有最后一点工作要做,如下:
如果SimpleFormController的sessionForm属性被设置为true的话,我们会将绑定后的Command对象存入HttpSession.这样表单提交之后,也即是
”处理表单提交阶段“,可以重新获取该Command对象,而不是又重新省生成一个
Command更多的面向表单的各个字段,与表单中的字段形成双向的绑定关系。但在某些情况下,表单页面显示还需要Command所包含之外的信息,
比如,如果我们的表单中需要提供某种动态信息的可选择下拉列表,那么这是可能就需要从需要数据库中抽取将构建下拉列表所需要的数据。这种
数据信息可以通过覆写referenceData()方法来提供,SimpleFormController将会把通过referenceData()返回的模型数据添加到即将返回的ModelAndView中
例如:
<pre name="code" class="java">protected Map referenceData(HttpServletRequest req)throws Exception{
Map map=new HashMap();
List cityList=new ArrayList();
City city1 = new City();
city1.setCityName("BeiJing");
city1.setCityNo("010");
cityList.add(city1);
City city2 = new City();
city2.setCityName("ShangHai");
city2.setCityNo("020");
cityList.add(city2);
map.put("cityList", cityList);
return map;
} <tr>
<td><spring:bind path="command.city">
城市:
<select name="${status.expression}">
<c:forEach items="${cityList}" var="city" varStatus="loopStep">
<option value="city.cityNo"
<c:if test="${city.cityNo==status.value}">selected</c:if>>
<c:out value="${city.cityName}" />
</option>
</c:forEach>
</select>
</spring:bind></td>
</tr>map中的cityList和jsp中items="${cityList}"对应
总的来说,referenceData()是返回的可供选择的数据列表,而Command通常只是保存数据列表中选择后的那个值
在以上准备工作完成之后,SimpleFormController就会将这些”搜刮“来的数据一并放到某个ModelAndView实例中并返回
(该ModelAndView将使用formView属性所指定的逻辑视图名)
处理表单提交阶段的流程分析
(1)获取要绑定的目标对象
用户提交表单之后,我们首先判断SimpleFormController的sessionForm属性是否为true,如果是,我们将从HttpSession中获取之前
已经存入的Command对象,否则,与表单显示阶段一样,通过formBackingObject方法重新生成一个Command对象以备继数据绑定之用
(2)初始化DataBinder,与表单显示阶段完全相同
(3)执行数据绑定/数据验证
在用户通过表单提交数据之后,我们需要将这些新的数据绑定到Command对象上,在绑定的实现上与”表单显示阶段“同一步骤原理相同
,只不过绑定完成后为我们提供的扩展点是onBind()方法,我们可以覆写该方法对绑定后的Commnand对象进行进一步的限定
例如:
protected void onBind(HttpServletRequest req,Object obj){
UserModel user=(UserModel)obj;
System.out.println(user.getPhone());
user.setAccount(user.getAccount().trim());
user.setPhone(user.getPhone().trim());
}与表单显示阶段不同,完成数据绑定后,还需要对用户提交的数据进行验证。
数据验证逻辑的执行实际上是不需要我们关心的,要实现服务器端的数据验证,我们所要做的只是为SimpleFormController指定一组
用于对当前表单提交的数据进行数据验证的Validator实现类就行了。
虽然Validator的调用不需要我们干预,但是如果需要在数据验证完成后对Command对象做一些手脚,可以通过覆写onBindAndValidate()
方法,就可以在继续剩下的逻辑之前插入一些自定义的逻辑
例如:
protected void obBindAndValidate(HttpServletRequest req,Object obj,BindException err)throws Exception{
UserModel user=(UserModel)obj;
user.setCreateTime(new Date());
}(4)处理表单提交
在允许我们调用相应的业务对象处理最终的数据之前,SimpleFormController还要做进一步的”安检“
SimpleFormController会首先检查数据验证后是否存在错误,如果存在错误,它将会把处理流程导向”显示表单阶段“的最后一步
也就是重新显示页面。如果没有验证错误,SimpleFormController再检查一下当前Web请求是否只是一个FormChangeRequest,也就是说
当前Web请求只是对表单的数据进行了一些变动,而不是最终的提交,这是依然不能处理数据,而是需要将试图重新导向表单的显示
页面,不过在将流程导向表单页面显示之前,我们可以通过覆写onFormChange方法添加自定义逻辑,不如更改Command对象数据状态。
在以上这些检查之后,就可以真正的处理表单提交的数据了,SimpleFormController允许我们在如下两个地方提供表单数据的处理逻辑实现
可以通过覆写方法doSubmitAction(Object command)来添加针对当前表单数据的处理逻辑,最常见的就是直接调用相应的服务对象。
doSubmitAction方法执行之后,SimpleFormController将会构建一个ModelAndView将视图导向successView属性所指定的视图
实际上,doSubmitAction只是onSubmit()方法所公开的一个回调方法,当前者执行完毕,是由onSubmit来构建相应的ModelAndView以
”打扫战场“的,所以,如果感觉doSubmitAction不能为我们提供更大的发挥余地,那么,我们完全可以覆写onSubmit()方法来实现具体
处理逻辑,只不过,在最后要自己构建一个ModelAndView并返回,通常情况下,直接覆写doSubmitAction方法就够了
在视图被导向successView属性所指定的页面之后,我们的表单处理流程就算结束了。
下面再用post和get方法对SimpleFormController进行总结:
get方法处理流程(一般连接的形式为get方法):
按请求转到相应controller;
调用formBackingObject()方法,创建一个command对象的实例(如果设定了commandClass,则不需要override formBackingObject()方法了);
调用initBinder(),注册需要的类型转换器;
调用showForm()方法,返回一个view,即准备呈现给用户的视图,一般情况下这个方法是不需要override的,只需设定formView即可;
调用referenceData()方法。这个方法返回一个Map对象,可以把在view中需要展现的数据放入这个Map中;
转到formView指定的视图。
一般情况下步骤2,3,4的方法是不需要override的,它们只需要在配置文件中进行相关属性的定义即可。而步骤5的referenceData()一般是要在自己的SimpleFormController中进行重新定义的。
post方法处理流程(一般用于处理表单):
按请求转到相应controller;
调用formBackingObject()方法,创建一个command对象的实例(如果设定了commandClass,则不需要override formBackingObject()方法了);
把请求参数注入表单对象;
执行onBind()方法;
执行validator验证。
执行onBindAndValidate()方法;
若有err,则转到formView视图;
执行onSubmit()方法或doSubmitAction()方法。
同处理get方法一样,步骤2的formBackingObject()方法一般是不会去重载的。步骤3也不需要我们做什么。真正需要我们做的是4,5,6,8步。当然4,5,6是可选的,如果有需要,
可以去实现它们中的一个或几个,执行的步骤是按上面的顺序。但步骤8的两个方法是必须选一个的(两个都override是没有意义的)。
这里要说明一下onSubmit()方法和doSubmitAction()方法。在SimpleFormController中,有三种形式的onSubmit方法,它们是:
onSubmit(req, res, command, errs);
onSubmit(command, errs);
onSubmit(command);
但是这三种方法不是孤立的,第一个方法在执行中会调用第二个方法,第二个方法在执行中会调用第三个方法。一般在定义自己的SimpleFormController时只是override onSubmit(command)方法。
doSubmitAction()方法是一个有意思的方法,它的完整定义如下:
protected void doSubmitAction(Object obj) throws Exception
它的返回类型是void,而不是我们预想的ModelAndView,并且也不返回任何视图层需要的数据。实际上doSubmitAction执行完毕后会自动转到successView视图。
并且如果配合sessionForm来使用的的话(sessionForm设为true),那么在转到successView视图后,在session中可以取到先前的表单对象
介绍了这些多,下面是我实现的别人的例子:
首先来看一下配置文件:
web.xml
<pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>SpringMVC_SimpleFormController</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
<strong></strong><pre name="code" class="html">springMVC.xml配置文件
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="login.do">login</prop>
</props>
</property>
</bean>
<bean id="userValidator" class="com.web.springMVC.validator.UserValidator"></bean>
<!-- 处理器 -->
<!-- “SimpleFormController”,他是一个简单表单控制器,这种控制器有三个基本的属性需要指定。这三个属性都是 simpleFormController这个父类自带属性,即当一个类继承了SimpleFormController类时,这三个属性就存在,用户不 需要再自行定义。 -->
<bean name="login" class="com.web.springMVC.controller.Login">
<property name="commandName">
<value>command</value>
</property>
<!-- 这里直接实现了将请求参数绑定到对象上 -->
<property name="commandClass">
<value>com.web.springMVC.model.UserModel</value>
</property>
<!-- 这里直接实现了数据验证 -->
<property name="validator" ref="userValidator"></property>
<!-- 下面设置了两个转向页面 -->
<!-- ormView 这个属性规定了控制器执行失败时返回到的视图。由于其值是“form”,又由于名为viewResolver的bean规定了后缀是.jsp,因此控制器执行成功时,打开名为”form.jsp”的网页 -->
<property name="formView" value="user"></property>
<!-- 返回处理成功的页面 -->
<!-- SimpleFormController将构建一个ModelAndView将视图导向successView属性指定的视图 -->
<!-- successView这个属性规定了控制器执行成功时返回的视图。在这里,由于其值是“success”,又由于名为viewResolver的bean规定了后缀是.jsp,控制器成功执行时,打开名为“success.jsp”网页。 -->
<property name="successView" value="success"></property>
</bean>
<!-- HandlerMapping -->
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- 定义跳转后的前后缀 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>这里需要对上面的配置文件进行说明。commandName属性的设置是为了可以在页面中使用Spring tag,对业务流程没有影响,如果不想使用Spring tag,则这个属性可以没有。commandClass指定了封装表单的类,注意,要指定完整的路径,
不能只指定一个类名,也不能<ref .../>。validator的设定说明了要使用验证器。formView和successView这两个属性设定了转向的页面,它们是父类所具有的,所以不需要在你的controller中再注入了。
下面来解释下SimpleFormController类中的属性
“SimpleFormController”,他是一个简单表单控制器,这种控制器有三个基本的属性需要指定。这三个属性都是 simpleFormController这个父类自带属性,即当一个类继承了SimpleFormController类时,这三个属性就存在,用户不 需要再自行定义。
1 commandClass 这个属性规定了存放表单数据的bean。便于spring 容器识别用户定义的数据接口。(是一个简单的bean)
2 formView 这个属性规定了控制器执行失败时返回到的视图。由于其值是“form”,又由于名为viewResolver的bean规定了后缀是.jsp,因此控制器执行成功时,打开名为”form.jsp”的网页.
3 successView这个属性规定了控制器执行成功时返回的视图。在这里,由于其值是“success”,又由于名为viewResolver的bean规定了后缀是.jsp,控制器成功执行时,打开名为“success.jsp”网页。
下面是验证器:
public class UserValidator implements Validator{
//support(Class)方法定义,是为了进一步限定Validator实现类的职责
@Override
public boolean supports(Class clazz) {
return clazz.equals(UserModel.class);
}
@Override
public void validate(Object obj, Errors err) {
UserModel user=(UserModel)obj;
if(user.getPhone().length()<7){
user.setCreateTime(null);
err.reject("phoneErr", "电话号码位数要大于7");
}
else if(user.getAge()<=0){
user.setCreateTime(null);
err.reject("ageErr", "年龄要大于0");
}
}
}验证器实现了Validator借口,supports和validate这两个方法是必须实现的。验证器的主要任务是对表单类进行验证,这时请求的数据已经封装到表单类里了。
下面是表单类:
public class UserModel {
private String account;
private String phone;
private int age;
private String city;
private Date createTime;
setter..getter...
public class City {
private String cityName;
private String cityNo;
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getCityNo() {
return cityNo;
}
public void setCityNo(String cityNo) {
this.cityNo = cityNo;
}
}下面是这个例子的主角 Controller类 Login.class
public class Login extends SimpleFormController{
/**
* 构建下拉列表所需要的数据可以通过referenceData方法来提供,SimpleFormController将会把通过referenceData返回的
* 模型数据添加到即将返回的ModelAndView中
*/
protected Map referenceData(HttpServletRequest req)throws Exception{
Map map=new HashMap();
List cityList=new ArrayList();
City city1 = new City();
city1.setCityName("BeiJing");
city1.setCityNo("010");
cityList.add(city1);
City city2 = new City();
city2.setCityName("ShangHai");
city2.setCityNo("020");
cityList.add(city2);
map.put("cityList", cityList);
return map;
}
/**
* command是对应到绑定页面的对象
* 添加针对当前表单数据的处理逻辑
* doSubmitAction(Object command)方法执行之后,SimpleFormController将构建一个ModelAndView将视图导向successView属性指定的视图
*//*
public void doSubmitAction(Object command){
}*/
/**
* 在数据绑定完成后,覆写该方法对绑定后的Command对象做进一步的定制
*/
protected void onBind(HttpServletRequest req,Object obj){
UserModel user=(UserModel)obj;
System.out.println(user.getPhone());
user.setAccount(user.getAccount().trim());
user.setPhone(user.getPhone().trim());
}
/**
* 完成数据绑定后需要对数据进行验证,要实现服务器端数据验证,我们所要做的只是为SimpleFormController指定一组用于对当前表单提交的数据进行验证
* 的Validator实现类就行了,虽然Validator的调用我们不能干预,但是,如果需要在数据验证完成后对Command对象做一些“手脚”,覆写onBindAndValidate()
* 方法,可以在继续剩下的逻辑之前插入一些自定义逻辑
*/
protected void obBindAndValidate(HttpServletRequest req,Object obj,BindException err)throws Exception{
UserModel user=(UserModel)obj;
user.setCreateTime(new Date());
}
/**
* 通过覆写doSubmitAction(Object command)来添加针对当前表单数据的处理逻辑,最常见的就是直接调用当前的服务对象
* doSubmitAction方法执行之后,SimpleFormController将构建一个ModelAndView将试图导向successView属性指定的视图
* 实际上doSubmitAction只是onSubmit方法所公开的一个回调方法,当前者执行完毕后,由onSubmit来构建相应的ModelAndView将
* 视图导向successView属性指定的视图,如果感觉doSubmitAction方法不能为我们提供更大的余地,那么完全可以直接写onSubmit方法
* 来实现具体的处理逻辑,只不过在最后要自己构建一个ModelAndView并返回,通常情况下,直接覆写doSubmitAction方法就足够了
*/
protected ModelAndView onSubmit(Object command)throws Exception{
UserModel user=(UserModel)command;
/**
* public ModelAndView(String viewName,String modelname,Object modelObject),这里只有一个数据对象
*/
return new ModelAndView(this.getSuccessView(),"user",user);
}
}
下面使用的user.jsp主要部分
<body>
<form action="login.do" method="post">
<table>
<tbody>
<tr>
<td>
<!-- "status"的"expression"會顯示綁定的屬 性名稱,而"value"則顯示表單物件中所儲存的值 --> <spring:bind
path="command.account">
用户名:<input name="${ status.expression}" type="text"
value="${status.value}" />
</spring:bind>
</td>
</tr>
<tr>
<td><spring:bind path="command.age">
年龄:<input name="${status.expression }" type="text"
value="${status.value }" />
</spring:bind></td>
</tr>
<tr>
<td><spring:bind path="command.phone">
电话:<input name="${status.expression}" type="text"
value="${status.value}" />
</spring:bind></td>
</tr>
<tr>
<td><spring:bind path="command.city">
城市:
<select name="${status.expression}">
<c:forEach items="${cityList}" var="city" varStatus="loopStep">
<option value="city.cityNo"
<c:if test="${city.cityNo==status.value}">selected</c:if>>
<c:out value="${city.cityName}" />
</option>
</c:forEach>
</select>
</spring:bind></td>
</tr>
<tr>
<td align="center"><input type="submit" value="提交" /></td>
</tr>
</tbody>
</table>
</form>
</body>success.jsp的主要部分
<body>
<table>
<tbody>
<tr>
<td>User Info:</td>
</tr>
<tr>
<td><c:out value="${user.account}"></c:out></td>
</tr>
<tr>
<td><c:out value="${user.age}"></c:out></td>
</tr>
<tr>
<td><c:out value="${user.phone}"></c:out></td>
</tr>
<tr>
<td><c:out value="${user.city}"></c:out></td>
</tr>
<tr>
<td><c:out value="${user.createTime}"></c:out></td>
</tr>
</tbody>
</table>
</body>在地址栏直接输入:http://localhost:8080/SpringMVC_SimpleFormController_1/login.do 会显示如下页面
本文详细介绍了SpringMVC的SimpleFormController,包括数据绑定、Spring框架的数据验证、表单显示和提交阶段的流程。SimpleFormController简化了Web请求处理,通过commandClass属性设置数据绑定目标,使用Validator进行数据验证,通过sessionForm和successView控制表单展示和提交后的流向。

被折叠的 条评论
为什么被折叠?



