SpringMVC关键问题讲解

接着上篇文章,大家可能关心的那两个问题

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 Tag比较少,不超过十个,这里只介绍最常用的一个<sping:bind>,也叫作Spring绑定

<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>

posted @ 2013-05-14 10:43  赵雪丹  阅读(281)  评论(0编辑  收藏  举报