接着上篇文章,大家可能关心的那两个问题
1.controller是怎样进行数据封装的
要说这个问题,我不得不说SimpleFormController了
SimpleFormController是AbstractFormController的具体实现,允许你在配置文件里通过successView和formView属性来配置成功视图(表单成功提交后要转向的页面)和表单视图(显示表单的页面);如果提交不合法(有三种可能:1.validator出错。2.bind错误,也就是说从请求中提取参数封装到command的过程中出现了类型转化错误,比如将一个含字母字符串转换为Integer。3.onBindAndValidate()方法出错),则会重新返回到表单视图;如果提交合法,onSubmit()方法的默认实现会转向成功页面,当然你可以覆写该方法在转向之前填充一些你想返回的信息。
SimpleFormController的工作流与AbstractFormController差不多,唯一的不同是你不必自己去实现showForm()和processFormSubmission()。showForm()这个方法已经被类SimpleFormController实现了并被限定为final,你不可以在继承SimpleFormController的子类里覆写这个类。processFormSubmission()这个方法尽管可以去覆写但由于它几乎可以满足所有的要求,因此一般也不会有人去重写它。
它的处理流程是这样的:
get请求来到时,这样处理:
1) 请求传递给一个controller对象
2) 调用formBackingObject()方法,创建一个command对象的实例。
3) 调用initBinder(),注册需要的类型转换器
4) 调用showForm()方法,返回准备呈现给用户的视图 ,如果“bindOnNewForm”属性设为true,则ServletRequestDataBinder会将初始请求参数填入一个新的表单对象,并且执行onBindOnNewForm()方法。
5) 调用referenceData()方法,准备给用户显示相关的数据。如用户登录需要选择的年度信息
6) 返回formView指定的视图
post请求来到时,这样处理:
1) 如果sessionForm属性没有设定,则调用formBackingObject()方法,创建一个command对象的实例。否则从session中取得表单对象
2) 将请求传来的参数写入command对象,看它的源代码,会发现它是这样来做的:
ServletRequestDataBinder binder = createBinder(request, command);
binder.bind(request);
3)执行onBind()方法,在绑定数据之后,验证数据之前对表单数据进行一些自制的修改动作。
4) 如果设置为要求验证(validateOnBinding属性被设定),则调用validator类进行数据验证
5) 调用onBindAndValidate()方法,该方法允许自定义数据绑定和校验处理
6)执行processFormSubmission()检验 Errors对象中含不含错误,如果含有错误则执行showForm()返回到填写表单页面;否则执行onSubmit()方法,进行提交表单,然后转向成功页面。
2.<spring:kind>的用法
在Spring框架体系下,可以说规约最少,最不受限制的就是表现层技术了。不像Struts,改定了好多的标签,而且有些功能还和标签绑定了。Sping也定义了一些标签,但这些标签只是给使用者提供了一些方便,并不会提供额外的功能或效果。
<sping:bind>的path属性制定了与表单中的那个属性绑定,这样,${status.expression}就代表了那个属性的名称,${status.expression}代表那个属性的值。如果path属性为“XXX.*”则与那个表单的所有属性绑定。
上面的例子表单有两个属性username和password。实际上绑定方式有两种,第一种就像我的上一篇http://javacrazyer.iteye.com/blog/790834文章讲的的那样,第二种如下:
......
<spring:bind path="loginForm">
用户名:<INPUT name="userName" type="text" value="${command.userName}"/><br>
密码:<INPUT name="password" type="password" value="${command.password}"/>
</spring:bind>
这样尽管也行,但在错误信息处理上,针对用户名和密码的报错信息不会像原来那样显示出来了
所以验证我下面要说的话:
使用<sping:bind>标签,在初次进入表单页面时并不会有什么作用,而是当表单提交后,如果有BindException错误时再返回这个页面时,可以把先前的输入显示在input里。
说到错误信息处理,我又要许多要说了,来看看我前一篇文章http://javacrazyer.iteye.com/blog/790834中controller中的
errors.reject("ccc", "用户名或密码有误!");
return new ModelAndView(getFormView(), errors.getModel());
它调用了BindException的reject方法,这样,再调用BindException的getModel()方法,就把错误连同表单等信息一并返回到表单页面用以显示。
reject方法的第一个参数是错误码,如果设定了国际化资源,则显示资源文件中该错误码对应的错误条目,如果没有设定了国际化资源,则显示reject方法的第二个参数。
reject方法的不足之处是在表现层不能区分错误消息属于那个字段,即不能说明是username不对呢还是password不对。解决这种情况可以使用rejectValue方法,这也是更一般使用的方法。rejectValue方法定义如下:
rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage)
第一个参数指定表单的域,即username或password,这样就可以分辨到底是那块出了问题;第二个参数和reject方法的第一个参数一样,制定错误码;第三个参选数制定了资源文件中的占位符;第四个参数和和reject方法的第二个参数一样。rejectValue方法还有一个简化的定义
rejectValue(String field, String errorCode, String defaultMessage)
上面是在Controller里使用的方法,使用上述方法后,若果出现BindException错误,返回表单页面时就会显示错误信息,那么如何在页面里显示错误信息呢?
上面/WEB-INF/jsp/login.jsp里由于在controller里使用的是reject方法,所以只能那么显示,如果我们使用rejectValue方法,例如改动LoginController:
errors.rejectValue("userName", "nameErr", null, "用户名错误");
errors.rejectValue("password", "passErr", null, "密码错误");
这样,就可以把页面改为如之前最后的形式
<spring:bind path="command.userName">
名称 <input type="text" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
<spring:bind path="command.password">
密码 <input type="password" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
这样错误的消息就绑定到相应的字段了。当然也可以不制定某个字段,一股脑都输出
到此,大家还是没看到我是怎样讲解command这个值的,至于为什么非要是command而不是其他值
这是因为setCommandClass这个方法是AbstractController中的一个方法,而这个
方法使用到的一个默认值public static final java.lang.String DEFAULT_COMMAND_NAME = "command";public static final java.lang.String DEFAULT_COMMAND_NAME = "command";
看到了没有,就是叫做command,所以在标签中就敢大胆的用啦
3.最后还有几个小问题
(1)一个常见的错误:
不通过controller直接访问含有spring:bind标签的JSP页面会出现下面的错误:
javax.servlet.ServletException: Neither Errors instance nor plain target object for bean name 'person' available as request attribute
解决办法:
http://spring.jactiongroup.net/viewtopic.php?p=5482
(2)星号(*)的意思
global and all field errors,
## use wildcard (*) in place of the property name
<spring:bind path="company.*">
<c:forEach items="${status.errorMessages}" var="error">
c:out value="${error}"/><br/>
</c:forEach>
</spring:bind>
(3)command的意思
3-1 commandClass
相当于struts中的ActionForm,用来封装V中的数据,方便在C中使用。
3-2 commandName
用来指定JSP中的数据需要绑定到哪个对象。默认为command
比如下面的配置中,commandName就是command
<spring:bind path='command.email'>
<td><input type='text' name='${status.expression}'
value='${status.value}' size='30'
maxlength='100'></td></tr>
</spring:bind>
因为是缺省值,所以它就不需要再在Controller中显示声明
如果在Controller中设置了setCommandName("me");则上面的配置文件需要改为:
<spring:bind path='me.email'>
<td><input type='text' name='${status.expression}'
value='${status.value}' size='30'
maxlength='100'></td></tr>
</spring:bind>
简单吧。
(4)一个要注意的问题(原文链接)
一个普通的<spring.bind>的使用类似于:
<spring:bind path="user.age">
<input type="text" name="age" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>
需要注意的是:<input>的name属性值必须与<spring:bind>的path属性的匹配,否则就绑定不了!
例如下面的代码就绑定不了
<spring:bind path="user.age">
<input type="text" name="theAge" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>
为了避免手误,强烈推荐下列方法来绑定:
<spring:bind path="user.age">
<input type="text" name="${status.expression}" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>