zl程序教程

您现在的位置是:首页 >  后端

当前栏目

SpringMVC(4.2):Controller接口控制器详解(2)

SpringMVC接口 详解 控制器 Controller 4.2
2023-09-27 14:21:41 时间

原文出处: 张开涛


4.5、ServletForwardingController

将接收到的请求转发到一个命名的servlet,具体示例如下:

  1 package cn.javass.chapter4.web.servlet;
  2 public class ForwardingServlet extends HttpServlet {
  3     @Override
  4     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
  5     throws ServletException, IOException {
  6 
  7         resp.getWriter().write("Controller forward to Servlet");
  8 
  9     }
 10 }
  1 <servlet>
  2     <servlet-name>forwarding</servlet-name>
  3     <servlet-class>cn.javass.chapter4.web.servlet.ForwardingServlet</servlet-class>
  4 </servlet>
  1 <!— 在chapter4-servlet.xml配置处理器 -->
  2 <bean name="/forwardToServlet"
  3 class="org.springframework.web.servlet.mvc.ServletForwardingController">
  4         <property name="servletName" value="forwarding"></property>
  5 </bean>



当我们请求/forwardToServlet时,会被转发到名字为“forwarding”的servlet处理,该sevlet的servlet-mapping标签配置是可选的。

4.6、BaseCommandController

命令控制器通用基类,提供了以下功能支持:

1、数据绑定:请求参数绑定到一个command object(命令对象,非GoF里的命令设计模式),这里的命令对象是指绑定请求参数的任何POJO对象;

   commandClass:表示命令对象实现类,如UserModel;

   commandName:表示放入请求的命令对象名字(默认command),request.setAttribute(commandName, commandObject);

2、验证功能:提供Validator注册功能,注册的验证器会验证命令对象属性数据是否合法;

   validators:通过该属性注入验证器,验证器用来验证命令对象属性是否合法;

该抽象类没有没有提供流程功能,只是提供了一些公共的功能,实际使用时需要使用它的子类。

4.7、AbstractCommandController

命令控制器之一,可以实现该控制器来创建命令控制器,该控制器能把自动封装请求参数到一个命令对象,而且提供了验证功能。

1、创建命令类(就是普通的JavaBean类/POJO)


  1 package cn.javass.chapter4.model;
  2 public class UserModel {
  3     private String username;
  4     private String password;
  5         //省略setter/getter  
  6 }

2、实现控制器

  1 package cn.javass.chapter4.web.controller;
  2 //省略import  
  3 public class MyAbstractCommandController extends AbstractCommandController {
  4     public MyAbstractCommandController() {
  5         //设置命令对象实现类  
  6         setCommandClass(UserModel.class);
  7     }
  8     @Override
  9     protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
 10         //将命令对象转换为实际类型  
 11         UserModel user = (UserModel) command;
 12         ModelAndView mv = new ModelAndView();
 13         mv.setViewName("abstractCommand");
 14         mv.addObject("user", user);
 15         return mv;
 16     }
 17 }
  1 <!— 在chapter4-servlet.xml配置处理器 -->
  2 <bean name="/abstractCommand"
  3 class="cn.javass.chapter4.web.controller.MyAbstractCommandController">
  4         <!-- 也可以通过依赖注入 注入命令实现类 -->
  5         <!-- property name="commandClass" value="cn.javass.chapter4.model.UserModel"/-->
  6 </bean>
  1 <!— WEB-INF/jsp/abstractCommand.jsp视图下的主要内容 -->
  2 
  3 ${user.username }-${user.password }



当我们在浏览器中输入“http://localhost:9080/springmvc-chapter4/abstractCommand?username=123&password=123”,会自动将请求参数username和password绑定到命令对象;绑定时按照JavaBean命名规范绑定;

a633b31a42f0224c9bf66cd3cc886e04__2


4.8、AbstractFormController

用于支持带步骤的表单提交的命令控制器基类,使用该控制器可以完成:

1、定义表单处理(表单的渲染),并从控制器获取命令对象构建表单;

2、提交表单处理,当用户提交表单内容后,AbstractFormController可以将用户请求的数据绑定到命令对象,并可以验证表单内容、对命令对象进行处理。

  1       @Override
  2 rotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
  3     throws Exception {
  4      //1、是否是表单提交? 该方法实现为("POST".equals(request.getMethod())),即POST表示表单提交  
  5 if (isFormSubmission(request)) {
  6     try {
  7         Object command = getCommand(request);
  8         ServletRequestDataBinder binder = bindAndValidate(request, command);
  9         BindException errors = new BindException(binder.getBindingResult());
 10               //表单提交应该放到该方法实现  
 11         return processFormSubmission(request, response, command, errors);
 12     }
 13     catch (HttpSessionRequiredException ex) {
 14               //省略部分代码  
 15         return handleInvalidSubmit(request, response);
 16     }
 17 }
 18 else {
 19     //2、表示是表单展示,该方法又转调showForm方法,因此我们需要覆盖showForm来完成表单展示  
 20     return showNewForm(request, response);
 21 }

bindOnNewForm:是否在进行表单展示时绑定请求参数到表单对象,默认false,不绑定;

sessionForm:session表单模式,如果开启(true)则会将表单对象放置到session中,从而可以跨越多次请求保证数据不丢失(多步骤表单常使用该方式,详解AbstractWizardFormController),默认false;

Object formBackingObject(HttpServletRequest request) :提供给表单展示时使用的表单对象(form object表单要展示的默认数据),默认通过commandName暴露到请求给展示表单;

Map referenceData(HttpServletRequest request, Object command, Errors errors):展示表单时需要的一些引用数据(比如用户注册,可能需要选择工作地点,这些数据可以通过该方法提供),如:


  1 protected Map referenceData(HttpServletRequest request) throws Exception {
  2              Map model = new HashMap();
  3              model.put("cityList", cityList);
  4              return model;
  5 }


    这样就可以在表单展示页面获取cityList数据。

SimpleFormController继承该类,而且提供了更简单的表单流程控制。

4.9、SimpleFormController

提供了更好的两步表单支持:

1、准备要展示的数据,并到表单展示页面;

2、提交数据数据进行处理。

第一步,展示:

a633b31a42f0224c9bf66cd3cc886e04__2

第二步,提交表单:

a633b31a42f0224c9bf66cd3cc886e04__2


接下来咱们写一个用户注册的例子学习一下:

(1、控制器


  1 package cn.javass.chapter4.web.controller;
  2 //省略import  
  3 public class RegisterSimpleFormController extends SimpleFormController {
  4     public RegisterSimpleFormController() {
  5         setCommandClass(UserModel.class); //设置命令对象实现类  
  6         setCommandName("user");//设置命令对象的名字  
  7     }
  8     //form object 表单对象,提供展示表单时的表单数据(使用commandName放入请求)  
  9     protected Object formBackingObject(HttpServletRequest request) throws Exception {
 10         UserModel user = new UserModel();
 11         user.setUsername("请输入用户名");
 12         return user;
 13     }
 14     //提供展示表单时需要的一些其他数据    
 15     protected Map referenceData(HttpServletRequest request) throws Exception {
 16         Map map = new HashMap();
 17         map.put("cityList", Arrays.asList("山东", "北京", "上海"));
 18         return map;
 19     }
 20     protected void doSubmitAction(Object command) throws Exception {
 21         UserModel user = (UserModel) command;
 22         //TODO 调用业务对象处理  
 23         System.out.println(user);
 24     }
 25 }


setCommandClasssetCommandName分别设置了命令对象的实现类和名字;

formBackingObjectreferenceData提供了表单展示需要的视图;

doSubmitAction用于执行表单提交动作,由onSubmit方法调用,如果不需要请求/响应对象或进行数据验证,可以直接使用doSubmitAction方法进行功能处理。

(2、spring配置(chapter4-servlet.xml

  1 <bean name="/simpleForm"
  2 class="cn.javass.chapter4.web.controller.RegisterSimpleFormController">
  3         <property name="formView" value="register"/>
  4         <property name="successView" value="redirect:/success"/>
  5 </bean>
  6 <bean name="/success" class="cn.javass.chapter4.web.controller.SuccessController"/>

formView表示展示表单时显示的页面;

successView表示处理成功时显示的页面;“redirect:/success”表示成功处理后重定向到/success控制器;防止表单重复提交;

/success” bean的作用是显示成功页面,此处就不列举了。

(3、视图页面

  1 <!-- register.jsp 注册展示页面-->
  2 <form method="post">
  3 username:<input type="text" name="username" value="${user.username}"><br/>
  4 password:<input type="password" name="username"><br/>
  5 city:<select>
  6   <c:forEach items="${cityList }" var="city">
  7    <option>${city}</option>
  8   </c:forEach>
  9 </select><br/>
 10 <input type="submit" value="注册"/>
 11 </form>



此处可以使用${user.username}获取到formBackingObject设置的表单对象、使用${cityList}获取referenceData设置的表单支持数据;

到此一个简单的两步表单到此结束,但这个表单有重复提交表单的问题,而且表单对象到页面的绑定是通过手工绑定的,后边我们会学习spring标签库(提供自动绑定表单对象到页面)。

4.10、CancellableFormController

一个可取消的表单控制器,继承SimpleFormController,额外提供取消表单功能。

1、表单展示:和SimpleFormController一样;

2、表单取消:和SimpleFormController一样;

3、表单成功提交:取消功能处理方法为:onCancel(Object command),而且默认返回cancelView属性指定的逻辑视图名。

   那如何判断是取消呢?如果请求中有参数名为“_cancel”的参数,则表示表单取消。也可以通过cancelParamKey来修改参数名(如“_cancel.x”等)。

a633b31a42f0224c9bf66cd3cc886e04__2



示例:

(1、控制器

复制RegisterSimpleFormController一份命名为CanCancelRegisterSimpleFormController,添加取消功能处理方法实现:

  1 @Override
  2 protected ModelAndView onCancel(Object command) throws Exception {
  3     UserModel user = (UserModel) command;
  4     //TODO 调用业务对象处理  
  5     System.out.println(user);
  6     return super.onCancel(command);
  7 }

onCancel在该功能方法内实现取消逻辑,父类的onCancel方法默认返回cancelView属性指定的逻辑视图名。

(2、spring配置(chapter4-servlet.xml

  1 <bean name="/canCancelForm"
  2 class="cn.javass.chapter4.web.controller.CanCancelRegisterSimpleFormController">
  3         <property name="formView" value="register"/>
  4         <property name="successView" value="redirect:/success"/>
  5         <property name="cancelView" value="redirect:/cancel"/>
  6 </bean>
  7 <bean name="/cancel" class="cn.javass.chapter4.web.controller.CancelController"/>

(3、视图页面(修改register.jsp

  1 <input type="submit" name="_cancel" value="取消"/>


该提交按钮的作用是取消,因为name="_cancel"即请求后会有一个名字为_cancel的参数,因此会执行onCancel功能处理方法。

(4、测试:

在浏览器输入“http://localhost:9080/springmvc-chapter4/canCancelForm”,则首先到展示视图页面,点击“取消按钮”将重定向到“http://localhost:9080/springmvc-chapter4/cancel”,说明取消成功了。

实际项目可能会出现比如一些网站的完善个人资料都是多个页面(即多步),那应该怎么实现呢?接下来让我们看一下spring Web MVC提供的对多步表单的支持类AbstractWizardFormController。


4.11、AbstractWizardFormController

向导控制器类提供了多步骤(向导)表单的支持(如完善个人资料时分步骤填写基本信息、工作信息、学校信息等)

假设现在做一个完善个人信息的功能,分三个页面展示:

1、页面1完善基本信息;

2、页面2完善学校信息;

3、页面3完善工作信息。

这里我们要注意的是当用户跳转到页面2时页面1的信息是需要保存起来的,还记得AbstractFormController中的sessionForm吗? 如果为true则表单数据存放到session中,哈哈,AbstractWizardFormController就是使用了这个特性。

a633b31a42f0224c9bf66cd3cc886e04__2


向导中的页码从0开始;

PARAM_TARGET = “_target”

用于选择向导中的要使用的页面参数名前缀,如“_target0”则选择第0个页面显示,即图中的“wizard/baseInfo”,以此类推,如“_target1”将选择第1页面,要得到的页码为去除前缀“_target”后的数字即是;

PARAM_FINISH = “_finish”

如果请求参数中有名为“_finish”的参数,表示向导成功结束,将会调用processFinish方法进行完成时的功能处理;

PARAM_CANCEL = “_cancel”

如果请求参数中有名为“_cancel”的参数,表示向导被取消,将会调用processCancel方法进行取消时的功能处理;

向导中的命令对象:

向导中的每一个步骤都会把相关的参数绑定到命令对象,该表单对象默认放置在session中,从而可以跨越多次请求得到该命令对象。

接下来具体看一下如何使用吧。

(1、修改我们的模型数据以支持多步骤提交:



  1 public class UserModel {
  2     private String username;
  3     private String password;
  4     private String realname; //真实姓名  
  5     private WorkInfoModel workInfo;
  6     private SchoolInfoModel schoolInfo;
  7     //省略getter/setter  
  8 }
  9 
 10 
 11 public class SchoolInfoModel {
 12     private String schoolType; //学校类型:高中、中专、大学  
 13     private String schoolName; //学校名称  
 14     private String specialty; //专业  
 15 //省略getter/setter  
 16 }
 17 
 18 
 19 public class WorkInfoModel {
 20     private String city; //所在城市  
 21     private String job; //职位  
 22     private String year; //工作年限  
 23 //省略getter/setter  
 24 }


(2、控制器

  1 package cn.javass.chapter4.web.controller;
  2 //省略import  
  3 public class InfoFillWizardFormController extends AbstractWizardFormController {
  4     public InfoFillWizardFormController() {
  5         setCommandClass(UserModel.class);
  6         setCommandName("user");
  7     }
  8     protected Map referenceData(HttpServletRequest request, int page) throws Exception {
  9         Map map = new HashMap();
 10         if(page==1) { //如果是填写学校信息页 需要学校类型信息  
 11             map.put("schoolTypeList", Arrays.asList("高中", "中专", "大学"));
 12         }
 13         if(page==2) {//如果是填写工作信息页 需要工作城市信息  
 14             map.put("cityList", Arrays.asList("济南", "北京", "上海"));
 15         }
 16         return map;
 17     }
 18     protected void validatePage(Object command, Errors errors, int page) {
 19         //提供每一页数据的验证处理方法  
 20     }
 21     protected void postProcessPage(HttpServletRequest request, Object command, Errors errors, int page) throws Exception {
 22         //提供给每一页完成时的后处理方法  
 23     }
 24     protected ModelAndView processFinish(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
 25         //成功后的处理方法  
 26         System.out.println(command);
 27         return new ModelAndView("redirect:/success");
 28     }
 29     protected ModelAndView processCancel(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
 30         //取消后的处理方法  
 31         System.out.println(command);
 32         return new ModelAndView("redirect:/cancel");
 33     }
 34 }


page页码:是根据请求中以“_target”开头的参数名来确定的,如“_target0”,则页码为0;

referenceData提供每一页需要的表单支持对象,如完善学校信息需要学校类型,page页码从0开始(而且根据请求参数中以“_target”开头的参数来确定当前页码,如_target1,则page=1);

validatePage验证当前页的命令对象数据,验证应根据page页码来分步骤验证;

postProcessPage验证成功后的后处理;

processFinish成功时执行的方法,此处直接重定向到/success控制器(详见CancelController);

processCancel取消时执行的方法,此处直接重定向到/cancel控制器(详见SuccessController);

其他需要了解:

allowDirtyBack和allowDirtyForward决定在当前页面验证失败时,是否允许向导前移和后退,默认false不允许;

onBindAndValidate(HttpServletRequest request, Object command, BindException errors, int page)允许覆盖默认的绑定参数到命令对象和验证流程。

(3spring配置文件(chapter4-servlet.xml

  1 <bean name="/infoFillWizard"
  2 class="cn.javass.chapter4.web.controller.InfoFillWizardFormController">
  3     <property name="pages">
  4         <list>
  5             <value>wizard/baseInfo</value>
  6             <value>wizard/schoolInfo</value>
  7             <value>wizard/workInfo</value>
  8        </list>
  9     </property>
 10 </bean>


(4、向导中的每一步视图

(4.1、基本信息页面(第一步) baseInfo.jsp
  1 <form method="post">
  2 真实姓名:<input type="text" name="realname" value="${user.realname}">
  3 <input type="submit" name="_target1" value="下一步"/>
  4 </form>
(4.2、学校信息页面(第二步) schoolInfo.jsp
  1 <form method="post">
  2 学校类型:<select name="schoolInfo.schoolType">
  3   <c:forEach items="${schoolTypeList }" var="schoolType">
  4    <option value="${schoolType }"
  5        <c:if test="${user.schoolInfo.schoolType eq schoolType}">
  6            selected="selected"
  7        </c:if>
  8    >
  9        ${schoolType}
 10    </option>
 11   </c:forEach>
 12 </select>
 13 学校名称:<input type="text" name="schoolInfo.schoolName" value="${user.schoolInfo.schoolName}"/>
 14 专业:<input type="text" name="schoolInfo.specialty" value="${user.schoolInfo.specialty}"/>
 15 <input type="submit" name="_target0" value="上一步"/>
 16 <input type="submit" name="_target2" value="下一步"/>
 17 </form>


(4.3、工作信息页面(第三步) workInfo.jsp
  1 <form method="post">
  2 所在城市:<select name="workInfo.city">
  3   <c:forEach items="${cityList }" var="city">
  4    <option value="${city }"
  5        <c:if test="${user.workInfo.city eq city}">selected="selected"</c:if>
  6    >
  7      ${city}
  8    </option>
  9   </c:forEach>
 10 </select>
 11 职位:<input type="text" name="workInfo.job" value="${user.workInfo.job}"/>
 12 工作年限:<input type="text" name="workInfo.year" value="${user.workInfo.year}"/>
 13 <input type="submit" name="_target1" value="上一步"/>
 14 <input type="submit" name="_finish" value="完成"/>
 15 <input type="submit" name="_cancel" value="取消"/>
 16 </form>
 17 


当前页码为2

name=”_target1″:上一步,表示向导上一步要显示的页面的页码为1;

name=”_finish”:向导完成,表示向导成功,将会调用向导控制器的processFinish方法

name=”_cancel”:向导取消,表示向导被取消,将会调用向导控制器的processCancel方法

到此向导控制器完成,此处的向导流程比较简单,如果需要更复杂的页面流程控制,可以选择使用Spring Web Flow框架。

4.12、ParameterizableViewController

参数化视图控制器,不进行功能处理(即静态视图),根据参数的逻辑视图名直接选择需要展示的视图。

  1 <bean name="/parameterizableView"
  2 class="org.springframework.web.servlet.mvc.ParameterizableViewController">
  3 <property name="viewName" value="success"/>
  4 </bean>


该控制器接收到请求后直接选择参数化的视图,这样的好处是在配置文件中配置,从而避免程序的硬编码,比如像帮助页面等不需要进行功能处理,因此直接使用该控制器映射到视图。

4.13、AbstractUrlViewController

提供根据请求URL路径直接转化为逻辑视图名的支持基类,即不需要功能处理,直接根据URL计算出逻辑视图名,并选择具体视图进行展示:

urlDecode是否进行url解码,不指定则默认使用服务器编码进行解码(如Tomcat默认ISO-8859-1);

urlPathHelper用于解析请求路径的工具类,默认为org.springframework.web.util.UrlPathHelper。

UrlFilenameViewController是它的一个实现者,因此我们应该使用UrlFilenameViewController。

4.14、UrlFilenameViewController

将请求的URL路径转换为逻辑视图名并返回的转换控制器,即不需要功能处理,直接根据URL计算出逻辑视图名,并选择具体视图进行展示:

根据请求URL路径计算逻辑视图名;

  1 <bean name="/index1/*"
  2 class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
  3 <bean name="/index2/**"
  4 class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
  5 <bean name="/*.html"
  6 class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
  7 <bean name="/index3/*.html"
  8 class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

/index1/*可以匹配/index1/demo,但不匹配/index1/demo/demo,如/index1/demo逻辑视图名为demo;

/index2/**可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo,“/index2/demo”的逻辑视图名为demo,而“/index2/demo/demo”逻辑视图名为demo/demo;

/*.html可以匹配如/abc.html,逻辑视图名为abc,后缀会被删除(不仅仅可以是html);

/index3/*.html可以匹配/index3/abc.html,逻辑视图名也是abc;

上述模式为Spring Web MVC使用的Ant-style 模式进行匹配的:

 

  1 
  2 ?    匹配一个字符,如/index? 可以匹配 /index1 , 但不能匹配 /index 或 /index12
  3 *    匹配零个或多个字符,如/index1/*,可以匹配/index1/demo,但不匹配/index1/demo/demo
  4 **   匹配零个或多个路径,如/index2/**:可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo
  5 
  6 如果我有如下模式,那Spring该选择哪一个执行呢?当我的请求为“/long/long”时如下所示:
  7 /long/long
  8 /long/**/abc
  9 /long/**
 10 /**
 11 Spring的AbstractUrlHandlerMapping使用:最长匹配优先;
 12 如请求为“/long/long” 将匹配第一个“/long/long”,但请求“/long/acd” 则将匹配 “/long/**”,如请求“/long/aa/abc”则匹配“/long/**/abc”,如请求“/abc”则将匹配“/**”


UrlFilenameViewController还提供了如下属性:

prefix生成逻辑视图名的前缀;

suffix生成逻辑视图名的后缀;

  1 protected String postProcessViewName(String viewName) {
  2         return getPrefix() + viewName + getSuffix();
  3 }
  1 <bean name="/*.htm" class="org.springframework.web.servlet.mvc.UrlFilenameViewController">
  2         <property name="prefix" value="test"/>
  3         <property name="suffix" value="test"/>
  4 </bean>

prefix=“test”,suffix=“test,如上所示的/*.htm可以匹配如/abc.htm,但逻辑视图名将变为testabctest。

4.15、MultiActionController

之前学过的控制器如AbstractCommandController、SimpleFormController等一般对应一个功能处理方法(如新增),如果我要实现比如最简单的用户增删改查(CRUD Create-Read-Update-Delete),那该怎么办呢?

4.15.1 解决方案

1、每一个功能对应一个控制器,如果是CRUD则需要四个控制器,但这样我们的控制器会暴增,肯定不可取;

2、使用spring Web MVC提供的MultiActionController,用于支持在一个控制器里添加多个功能处理方法,即将多个请求的处理方法放置到一个控制器里,这种方式不错。

4.15.2 问题

1、  MultiActionController如何将不同的请求映射不同的请求的功能处理方法呢?

Spring Web MVC提供了MethodNameResolver(方法名解析器)用于解析当前请求到需要执行的功能处理方法的方法名。默认使用InternalPathMethodNameResolver实现类,另外还提供了ParameterMethodNameResolver和PropertiesMethodNameResolver,当然我们也可以自己来实现,稍候我们仔细研究下它们是如何工作的。

2、那我们的功能处理方法应该怎么写呢?

public (ModelAndView | Map | String | void) actionName(HttpServletRequest request, HttpServletResponse response, [,HttpSession session] [,AnyObject]);

哦,原来如此,我们只需要按照如上格式写我们的功能处理方法即可;此处需要注意一下几点:

1、返回值:即模型和视图部分;

ModelAndView:模型和视图部分,之前已经见过了;

Map:只返回模型数据,逻辑视图名会根据RequestToViewNameTranslator实现类来计算,稍候讨论;

String:只返回逻辑视图名;

void:表示该功能方法直接写出response响应(如果其他返回值类型(如Map)返回null则和void进行相同的处理);

2、actionName功能方法名字;由methodNameResolver根据请求信息解析功能方法名,通过反射调用;

3、形参列表:顺序固定,“[]”表示可选,我们来看看几个示例吧:

  1 //表示到新增页面
  2 
  3 public ModelAndView toAdd(HttpServletRequest request, HttpServletResponse response);
  4 
  5 //表示新增表单提交,在最后可以带着命令对象
  6 
  7 public ModelAndView add(HttpServletRequest request, HttpServletResponse response, UserModel user);
  8 
  9 //列表,但只返回模型数据,视图名会通过RequestToViewNameTranslator实现来计算
 10 
 11 public Map list(HttpServletRequest request, HttpServletResponse response);
 12 
 13 //文件下载,返回值类型为void,表示该功能方法直接写响应
 14 
 15 public void fileDownload(HttpServletRequest request, HttpServletResponse response)
 16 
 17 //第三个参数可以是session
 18 
 19 public ModelAndView sessionWith(HttpServletRequest request, HttpServletResponse response, HttpSession session);
 20 
 21 //如果第三个参数是session,那么第四个可以是命令对象,顺序必须是如下顺序
 22 
 23 public void sessionAndCommandWith(HttpServletRequest request, HttpServletResponse response, HttpSession session, UserModel user)
 24 

4、异常处理方法,MultiActionController提供了简单的异常处理,即在请求的功能处理过程中遇到异常会交给异常处理方法进行处理,式如下所示:

  1 public ModelAndView anyMeaningfulName(HttpServletRequest request, HttpServletResponse response, ExceptionClass exception)
  2 
  3 MultiActionController会使用最接近的异常类型来匹配对应的异常处理方法,示例如下所示:
  4 
  5 //处理PayException
  6 
  7 public ModelAndView processPayException(HttpServletRequest request, HttpServletResponse response, PayException ex)
  8 
  9 //处理Exception
 10 
 11 public ModelAndView processException(HttpServletRequest request, HttpServletResponse response,  Exception ex)
 12 

4.15.3 MultiActionController类实现

类定义:public class MultiActionController extends AbstractController implements LastModified ,继承了AbstractController,并实现了LastModified接口,默认返回-1;

核心属性:

delegate功能处理的委托对象,即我们要调用请求处理方法所在的对象,默认是this;

methodNameResolver功能处理方法名解析器,即根据请求信息来解析需要执行的delegate的功能处理方法的方法名。

核心方法:

  1 //判断方法是否是功能处理方法
  2 private boolean isHandlerMethod(Method method) {
  3     //得到方法返回值类型
  4     Class returnType = method.getReturnType();
  5     //返回值类型必须是ModelAndView、Map、String、void中的一种,否则不是功能处理方法
  6     if (ModelAndView.class.equals(returnType) || Map.class.equals(returnType) || String.class.equals(returnType) ||
  7             void.class.equals(returnType)) {
  8         Class[] parameterTypes = method.getParameterTypes();
  9         //功能处理方法参数个数必须>=2,且第一个是HttpServletRequest类型、第二个是HttpServletResponse
 10         //且不能Controller接口的handleRequest(HttpServletRequest request, HttpServletResponse response),这个方法是由系统调用
 11         return (parameterTypes.length >= 2 &&
 12                 HttpServletRequest.class.equals(parameterTypes[0]) &&
 13                 HttpServletResponse.class.equals(parameterTypes[1]) &&
 14                 !("handleRequest".equals(method.getName()) && parameterTypes.length == 2));
 15     }
 16     return false;
 17 }


  1 //是否是异常处理方法
  2 private boolean isExceptionHandlerMethod(Method method) {
  3     //异常处理方法必须是功能处理方法 且 参数长度为3、第三个参数类型是Throwable子类
  4     return (isHandlerMethod(method) &&
  5             method.getParameterTypes().length == 3 &&
  6             Throwable.class.isAssignableFrom(method.getParameterTypes()[2]));
  7 }
  8 
  9 
 10 
 11 
 12 
 13 
 14 private void registerHandlerMethods(Object delegate) {
 15     //缓存Map清空
 16     this.handlerMethodMap.clear();
 17     this.lastModifiedMethodMap.clear();
 18     this.exceptionHandlerMap.clear();
 19 
 20     //得到委托对象的所有public方法
 21     Method[] methods = delegate.getClass().getMethods();
 22     for (Method method : methods) {
 23         //验证是否是异常处理方法,如果是放入exceptionHandlerMap缓存map
 24         if (isExceptionHandlerMethod(method)) {
 25             registerExceptionHandlerMethod(method);
 26         }
 27         //验证是否是功能处理方法,如果是放入handlerMethodMap缓存map
 28         else if (isHandlerMethod(method)) {
 29             registerHandlerMethod(method);
 30             registerLastModifiedMethodIfExists(delegate, method);
 31         }
 32     }
 33 }
 34 
 35 
 36 
 37 
 38 
 39 protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
 40         throws Exception {
 41     try {
 42         //1、使用methodNameResolver 方法名解析器根据请求解析到要执行的功能方法的方法名
 43         String methodName = this.methodNameResolver.getHandlerMethodName(request);
 44         //2、调用功能方法(通过反射调用,此处就粘贴代码了)
 45         return invokeNamedMethod(methodName, request, response);
 46     }
 47     catch (NoSuchRequestHandlingMethodException ex) {
 48         return handleNoSuchRequestHandlingMethod(ex, request, response);
 49     }
 50 }
 51 
 52 
 53 
 54 


接下来,我们看一下MultiActionController如何使用MethodNameResolver来解析请求到功能处理方法的方法名。

4.15.4 MethodNameResolver

1、InternalPathMethodNameResolver:MultiActionController的默认实现,提供从请求URL路径解析功能方法的方法名,从请求的最后一个路径(/)开始,并忽略扩展名;如请求URL是“/user/list.html”,则解析的功能处理方法名为“list”,即调用list方法。该解析器还可以指定前缀和后缀,通过prefix和suffix属性,如指定prefix=”test_”,则功能方法名将变为test_list;

2、ParameterMethodNameResolver:提供从请求参数解析功能处理方法的方法名,并按照如下顺序进行解析:

(1、 methodParamNames:根据请求的参数名解析功能方法名(功能方法名和参数名同名);

  1 <property name="methodParamNames" value="list,create,update"/>

如上配置时,如果请求中含有参数名list、create、update时,则功能处理方法名为list、create、update,这种方式的可以在当一个表单有多个提交按钮时使用,不同的提交按钮名字不一样即可。

  ParameterMethodNameResolver也考虑到图片提交按钮提交问题:

    <input type="image" name="list"> 和submit类似可以提交表单,单击该图片后会发送两个参数“list.x=x轴坐标”和“list.y=y轴坐标”(如提交后会变为list.x=7&list.y=5);因此我们配置的参数名(如list)在会加上“.x” 和 “.y”进行匹配。


  1 for (String suffix : SUBMIT_IMAGE_SUFFIXES)  {//SUBMIT_IMAGE_SUFFIXES {“.x”, “.y”}
  2     if (request.getParameter(name + suffix) != null) {// name是我们配置的methodParamNames
  3         return true;
  4     }
  5 }

(2、paramName:根据请求参数名的值解析功能方法名,默认的参数名是action,即请求的参数中含有“action=query”,则功能处理方法名为query;

(3、logicalMappings:逻辑功能方法名到真实功能方法名映射,如下所示:

  1 <property name="logicalMappings">
  2     <props>
  3         <prop key="doList">list</prop>
  4     </props>
  5 </property>


即如果步骤1或2解析出逻辑功能方法名为doList(逻辑的),将会被重新映射为list功能方法名(真正执行的)。

(4、defaultMethodName:默认的方法名,当以上策略失败时默认调用的方法名。

3、PropertiesMethodNameResolver:提供自定义的从请求URL解析功能方法的方法名,使用一组用户自定义的模式到功能方法名的映射,映射使用Properties对象存放,具体配置示例如下:

  1 <bean id="propertiesMethodNameResolver"
  2 class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
  3     <property name="mappings">
  4         <props>
  5               <prop key="/create">create</prop>
  6               <prop key="/update">update</prop>
  7               <prop key="/delete">delete</prop>
  8               <prop key="/list">list</prop>
  9               <!-- 默认的行为 -->
 10               <prop key="/**">list</prop>
 11         </props>
 12     </property>
 13 </bean>

对于/create请求将调用create方法,Spring内部使用PathMatcher进行匹配(默认实现是AntPathMatcher)。

4.15.5 RequestToViewNameTranslator

用于直接将请求转换为逻辑视图名。默认实现为DefaultRequestToViewNameTranslator。

1、DefaultRequestToViewNameTranslator:将请求URL转换为逻辑视图名,默认规则如下:

  http://localhost:9080/web上下文/list -------> 逻辑视图名为list

  http://localhost:9080/web上下文/list.html -------> 逻辑视图名为list(默认删除扩展名)

  http://localhost:9080/web上下文/user/list.html -------> 逻辑视图名为user/list

4.15.6 示例

(1、控制器UserController

  1 package cn.javass.chapter4.web.controller;
  2 //省略import
  3 public class UserController extends MultiActionController {
  4     //用户服务类
  5     private UserService userService;
  6     //逻辑视图名 通过依赖注入方式注入,可配置
  7     private String createView;
  8     private String updateView;
  9     private String deleteView;
 10     private String listView;
 11     private String redirectToListView;
 12     //省略setter/getter
 13 
 14     public String create(HttpServletRequest request, HttpServletResponse response, UserModel user) {
 15         if("GET".equals(request.getMethod())) {
 16             //如果是get请求 我们转向 新增页面
 17             return getCreateView();
 18         }
 19         userService.create(user);
 20         //直接重定向到列表页面
 21         return getRedirectToListView();
 22     }
 23     public ModelAndView update(HttpServletRequest request, HttpServletResponse response, UserModel user) {
 24         if("GET".equals(request.getMethod())) {
 25             //如果是get请求 我们转向更新页面
 26             ModelAndView mv = new ModelAndView();
 27             //查询要更新的数据
 28             mv.addObject("command", userService.get(user.getUsername()));
 29             mv.setViewName(getUpdateView());
 30             return mv;
 31         }
 32         userService.update(user);
 33         //直接重定向到列表页面
 34         return new ModelAndView(getRedirectToListView());
 35     }
 36 
 37     public ModelAndView delete(HttpServletRequest request, HttpServletResponse response, UserModel user) {
 38         if("GET".equals(request.getMethod())) {
 39             //如果是get请求 我们转向删除页面
 40             ModelAndView mv = new ModelAndView();
 41             //查询要删除的数据
 42             mv.addObject("command", userService.get(user.getUsername()));
 43             mv.setViewName(getDeleteView());
 44             return mv;
 45         }
 46         userService.delete(user);
 47         //直接重定向到列表页面
 48         return new ModelAndView(getRedirectToListView());
 49     }
 50 
 51     public ModelAndView list(HttpServletRequest request, HttpServletResponse response) {
 52         ModelAndView mv = new ModelAndView();
 53         mv.addObject("userList", userService.list());
 54         mv.setViewName(getListView());
 55         return mv;
 56     }
 57 
 58     //如果使用委托方式,命令对象名称只能是command
 59     protected String getCommandName(Object command) {
 60         //命令对象的名字 默认command
 61         return "command";
 62     }
 63 }

增删改:如果是GET请求方法,则表示到展示页面,POST请求方法表示真正的功能操作;

  getCommandName:表示是命令对象名字,默认command,对于委托对象实现方式无法改变,因此我们就使用默认的吧。

(2、spring配置文件chapter4-servlet.xml

  1 <bean id="userService" class="cn.javass.chapter4.service.UserService"/>
  2 <bean name="/user/**" class="cn.javass.chapter4.web.controller.UserController">
  3     <property name="userService" ref="userService"/>
  4     <property name="createView" value="user/create"/>
  5     <property name="updateView" value="user/update"/>
  6     <property name="deleteView" value="user/delete"/>
  7     <property name="listView" value="user/list"/>
  8     <property name="redirectToListView" value="redirect:/user/list"/>
  9     <!-- 使用PropertiesMethodNameResolver来解析功能处理方法名 -->
 10     <!--property name="methodNameResolver" ref="propertiesMethodNameResolver"/-->
 11 </bean>


userService:用户服务类,实现业务逻辑;

依赖注入:对于逻辑视图页面通过依赖注入方式注入,redirectToListView表示增删改成功后重定向的页面,防止重复表单提交;

默认使用InternalPathMethodNameResolver解析请求URL到功能方法名。

(3、视图页面

(3.1、list页面(WEB-INF/jsp/user/list.jsp)

  1 <a href="${pageContext.request.contextPath}/user/create">用户新增</a>
  2 <table border="1" width="50%">
  3    <tr>
  4       <th>用户名</th>
  5       <th>真实姓名</th>
  6       <th>操作</th>
  7    </tr>
  8    <c:forEach items="${userList}" var="user">
  9    <tr>
 10       <td>${user.username }</td>
 11       <td>${user.realname }</td>
 12       <td>
 13           <a href="${pageContext.request.contextPath}/user/update?username=${user.username}">更新</a>
 14           |
 15           <a href="${pageContext.request.contextPath}/user/delete?username=${user.username}">删除</a>
 16       </td>
 17    </tr>
 18    </c:forEach>
 19 </table>


(3.2、update页面(WEB-INF/jsp/user/update.jsp)

  1 <form action="${pageContext.request.contextPath}/user/update" method="post">
  2 用户名: <input type="text" name="username" value="${command.username}"/>
  3 真实姓名:<input type="text" name="realname" value="${command.realname}"/>
  4 <input type="submit" value="更新"/>
  5 </form>


(4、测试:

默认的InternalPathMethodNameResolver将进行如下解析:

http://localhost:9080/springmvc-chapter4/user/list————>list方法名;

http://localhost:9080/springmvc-chapter4/user/create————>create方法名;

http://localhost:9080/springmvc-chapter4/user/update————>update功能处理方法名;

http://localhost:9080/springmvc-chapter4/user/delete————>delete功能处理方法名。

我们可以将默认的InternalPathMethodNameResolver改为PropertiesMethodNameResolver

  1 <bean id="propertiesMethodNameResolver"
  2 class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
  3     <property name="mappings">
  4         <props>
  5               <prop key="/user/create">create</prop>
  6               <prop key="<span style="font-size: 1em; line-height: 1.5;">/user/</span><span style="font-size: 1em; line-height: 1.5;">update">update</prop></span>
  7               <prop key="<span style="font-size: 1em; line-height: 1.5;">/user/</span><span style="font-size: 1em; line-height: 1.5;">delete">delete</prop></span>
  8               <prop key="<span style="font-size: 1em; line-height: 1.5;">/user/</span><span style="font-size: 1em; line-height: 1.5;">list">list</prop></span>
  9               <prop key="/**">list</prop><!-- 默认的行为 -->
 10           </props>
 11     </property>
 12     <property name="alwaysUseFullPath" value="false"/><!-- 不使用全路径 -->
 13 </bean>
 14 <bean name="/user/**" class="cn.javass.chapter4.web.controller.UserController">
 15        <!—省略其他配置,详见配置文件-->
 16        <!-- 使用PropertiesMethodNameResolver来解析功能处理方法名 -->
 17        <property name="methodNameResolver" ref="propertiesMethodNameResolver"/>
 18 </bean>

/**表示默认解析到list功能处理方法。

如上配置方式可以很好的工作,但必须继承MultiActionController,Spring Web MVC提供给我们无需继承MultiActionController实现方式,即使有委托对象方式,继续往下看吧。

4.15.7、委托方式实现

(1、控制器UserDelegate

   将UserController复制一份,改名为UserDelegate,并把继承MultiActionController去掉即可,其他无需改变。

(2、spring配置文件chapter4-servlet.xml

  1 <!—委托对象-->
  2 <bean id="userDelegate" class="cn.javass.chapter4.web.controller.UserDelegate">
  3    <property name="userService" ref="userService"/>
  4    <property name="createView" value="user2/create"/>
  5    <property name="updateView" value="user2/update"/>
  6    <property name="deleteView" value="user2/delete"/>
  7    <property name="listView" value="user2/list"/>
  8    <property name="redirectToListView" value="redirect:/user2/list"/>
  9 </bean>
 10 <!—控制器对象-->
 11 <bean name="/user2/**"
 12 class="org.springframework.web.servlet.mvc.multiaction.MultiActionController">
 13 <property name="delegate" ref="userDelegate"/>
 14     <property name="methodNameResolver" ref="parameterMethodNameResolver"/>
 15 </bean>

delegate控制器对象通过delegate属性指定委托对象,即实际调用delegate委托对象的功能方法。

methodNameResolver此处我们使用ParameterMethodNameResolver解析器;

  1 <!—ParameterMethodNameResolver -->
  2 <bean id="parameterMethodNameResolver"
  3 class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  4 <!-- 1、根据请求参数名解析功能方法名 -->
  5     <property name="methodParamNames" value="create,update,delete"/>
  6     <!-- 2、根据请求参数名的值解析功能方法名 -->
  7     <property name="paramName" value="action"/>
  8 <!-- 3、逻辑方法名到真实方法名的映射 -->
  9     <property name="logicalMappings">
 10        <props>
 11            <prop key="doList">list</prop>
 12        </props>
 13     </property>
 14     <!—4、默认执行的功能处理方法 -->
 15     <property name="defaultMethodName" value="list"/>
 16 </bean>



1、methodParamNamescreate,update,delete,当请求中有参数名为这三个的将被映射为功能方法名,如“<input type=”submit” name=”create” value=”新增”/>”提交后解析得到的功能方法名为create;

2、paramName:当请求中有参数名为action,则将值映射为功能方法名,如“<input type=”hidden”name=”action” value=”delete”/>”,提交后解析得到的功能方法名为delete;

3、logicalMappings:逻辑功能方法名到真实功能方法名的映射,如:

http://localhost:9080/springmvc-chapter4/user2?action=doList;

    首先请求参数“action=doList”,则第二步解析得到逻辑功能方法名为doList;

    本步骤会把doList再转换为真实的功能方法名list。

4、defaultMethodName:以上步骤如果没有解析到功能处理方法名,默认执行的方法名。

(3、视图页面

(3.1、list页面(WEB-INF/jsp/user2/list.jsp)

  1 <a href="${pageContext.request.contextPath}/user2?action=create">用户新增</a>
  2 <table border="1" width="50%">
  3    <tr>
  4       <th>用户名</th>
  5       <th>真实姓名</th>
  6       <th>操作</th>
  7    </tr>
  8    <c:forEach items="${userList}" var="user">
  9    <tr>
 10       <td>${user.username }</td>
 11       <td>${user.realname }</td>
 12       <td>
 13           <a href="${pageContext.request.contextPath}/user2?action=update&username=${user.username}">更新</a>
 14           |
 15           <a href="${pageContext.request.contextPath}/user2?action=delete&username=${user.username}">删除</a>
 16       </td>
 17    </tr>
 18    </c:forEach>
 19 </table>

(3.2、update页面(WEB-INF/jsp/user2/update.jsp)

  1 <form action="${pageContext.request.contextPath}/user2" method="post">
  2 <input type="hidden" name="action" value="update"/>
  3 用户名: <input type="text" name="username" value="${command.username}"/>
  4 真实姓名:<input type="text" name="realname" value="${command.realname}"/>
  5 <input type="submit" value="更新"/>
  6 </form>


通过参数name=“action” value=“update”来指定要执行的功能方法名update。

(3.3、create页面(WEB-INF/jsp/user2/create.jsp)

  1 <form action="${pageContext.request.contextPath}/user2" method="post">
  2 用户名: <input type="text" name="username" value="${command.username}"/>
  3 真实姓名:<input type="text" name="realname" value="${command.realname}"/>
  4 <input type="submit" name="create" value="新增"/>
  5 </form>

(4、测试:

使用ParameterMethodNameResolver将进行如下解析:

http://localhost:9080/springmvc-chapter4/user2?create      ————>create功能处理方法名(参数名映射);

http://localhost:9080/springmvc-chapter4/user2?action=create————>create功能处理方法名(参数值映射);

http://localhost:9080/springmvc-chapter4/user2?update      ————>update功能处理方法名;

http://localhost:9080/springmvc-chapter4/user2?action=update————>update功能处理方法名;

http://localhost:9080/springmvc-chapter4/user2?delete      ————>delete功能处理方法名;

http://localhost:9080/springmvc-chapter4/user2?action=delete————>delete功能处理方法名;

http://localhost:9080/springmvc-chapter4/user2?doList      ————>通过logicalMappings解析为list功能处理方法。

http://localhost:9080/springmvc-chapter4/user2?action=doList————>通过logicalMappings解析为list功能处理方法。

http://localhost:9080/springmvc-chapter4/user2————>默认的功能处理方法名list(默认)。



4.16、数据类型转换和数据验证

a633b31a42f0224c9bf66cd3cc886e04__2


流程:

1、首先创建数据绑定器,在此此会创建ServletRequestDataBinder类的对象,并设置messageCodesResolver(错误码解析器);

2、提供第一个扩展点,初始化数据绑定器,在此处我们可以覆盖该方法注册自定义的PropertyEditor(请求参数——>命令对象属性的转换);

3、进行数据绑定,即请求参数——>命令对象的绑定;

4、提供第二个扩展点,数据绑定完成后的扩展点,此处可以实现一些自定义的绑定动作;

5、验证器对象的验证,验证器通过validators注入,如果验证失败,需要把错误信息放入Errors(此处使用BindException实现);

6、提供第三个扩展点,此处可以实现自定义的绑定/验证逻辑;

7、将errors传入功能处理方法进行处理,功能方法应该判断该错误对象是否有错误进行相应的处理。

4.16.1、数据类型转换

请求参数(String)——>命令对象属性(可能是任意类型)的类型转换,即数据绑定时的类型转换,使用PropertyEditor实现绑定时的类型转换。

一、spring内建的PropertyEditor如下所示:

image

二、Spring内建的PropertyEditor支持的属性(符合JavaBean规范)操作:


image

三、示例:

接下来我们写自定义的属性编辑器进行数据绑定:

1、模型对象:

  1 package cn.javass.chapter4.model;
  2 //省略import
  3 public class DataBinderTestModel {
  4     private String username;
  5     private boolean bool;//Boolean值测试
  6     private SchoolInfoModel schooInfo;
  7     private List hobbyList;//集合测试,此处可以改为数组/Set进行测试
  8     private Map map;//Map测试
  9     private PhoneNumberModel phoneNumber;//String->自定义对象的转换测试
 10     private Date date;//日期类型测试
 11     private UserState state;//String——>Enum类型转换测试
 12     //省略getter/setter
 13 }
 14 
 15 package cn.javass.chapter4.model;
 16 //如格式010-12345678
 17 public class PhoneNumberModel {
 18     private String areaCode;//区号
 19     private String phoneNumber;//电话号码
 20     //省略getter/setter
 21 }

(2PhoneNumber属性编辑器

前台输入如010-12345678自动转换为PhoneNumberModel。

  1 package cn.javass.chapter4.web.controller.support.editor;
  2 //省略import
  3 public class PhoneNumberEditor extends PropertyEditorSupport {
  4     Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
  5     @Override
  6     public void setAsText(String text) throws IllegalArgumentException {
  7         if(text == null || !StringUtils.hasLength(text)) {
  8             setValue(null); //如果没值,设值为null
  9         }
 10         Matcher matcher = pattern.matcher(text);
 11         if(matcher.matches()) {
 12             PhoneNumberModel phoneNumber = new PhoneNumberModel();
 13             phoneNumber.setAreaCode(matcher.group(1));
 14             phoneNumber.setPhoneNumber(matcher.group(2));
 15             setValue(phoneNumber);
 16         } else {
 17             throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", text));
 18         }
 19     }
 20     @Override
 21     public String getAsText() {
 22         PhoneNumberModel phoneNumber = ((PhoneNumberModel)getValue());
 23         return phoneNumber == null ? "" : phoneNumber.getAreaCode() + "-" + phoneNumber.getPhoneNumber();
 24     }
 25 }
 26 

PropertyEditorSupport一个PropertyEditor的支持类;

setAsText表示将String——>PhoneNumberModel,根据正则表达式进行转换,如果转换失败抛出异常,则接下来的验证器会进行验证处理;

getAsText表示将PhoneNumberModel——>String。

3、控制器

需要在控制器注册我们自定义的属性编辑器。

此处我们使用AbstractCommandController,因为它继承了BaseCommandController,拥有绑定流程。

  1 package cn.javass.chapter4.web.controller;
  2 //省略import
  3 public class DataBinderTestController extends AbstractCommandController {
  4     public DataBinderTestController() {
  5         setCommandClass(DataBinderTestModel.class); //设置命令对象
  6         setCommandName("dataBinderTest");//设置命令对象的名字
  7     }
  8     @Override
  9     protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
 10         //输出command对象看看是否绑定正确
 11         System.out.println(command);
 12         return new ModelAndView("bindAndValidate/success").addObject("dataBinderTest", command);
 13     }
 14     @Override
 15     protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
 16         super.initBinder(request, binder);
 17         //注册自定义的属性编辑器
 18         //1、日期
 19         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 20         CustomDateEditor dateEditor = new CustomDateEditor(df, true);
 21         //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
 22         binder.registerCustomEditor(Date.class, dateEditor);
 23         //自定义的电话号码编辑器
 24         binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
 25     }
 26 }


initBinder:第一个扩展点,初始化数据绑定器,在此处我们注册了两个属性编辑器;

CustomDateEditor自定义的日期编辑器,用于在String<——>日期之间转换;

binder.registerCustomEditor(Date.class, dateEditor):表示如果命令对象是Date类型,则使用dateEditor进行类型转换;

PhoneNumberEditor自定义的电话号码属性编辑器用于在String<——> PhoneNumberModel之间转换;

binder.registerCustomEditor(PhoneNumberModel.class, newPhoneNumberEditor()):表示如果命令对象是PhoneNumberModel类型,则使用PhoneNumberEditor进行类型转换;

(4、spring配置文件chapter4-servlet.xml

  1 <bean name="/dataBind"
  2 class="cn.javass.chapter4.web.controller.DataBinderTestController"/>


5、视图页面(WEB-INF/jsp/bindAndValidate/success.jsp

  1 EL phoneNumber:${dataBinderTest.phoneNumber}
  2 EL state:${dataBinderTest.state}
  3 EL date:${dataBinderTest.date}

视图页面的数据没有预期被格式化,如何进行格式化显示呢?请参考【第七章  注解式控制器的数据验证、类型转换及格式化】。

6、测试:

1、在浏览器地址栏输入请求的URL,如

http://localhost:9080/springmvc-chapter4/dataBind?username=zhang&bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&phoneNumber=010-12345678&date=2012-3-18 16:48:48&state=blocked

2、控制器输出的内容:

DataBinderTestModel [username=zhang, bool=true, schooInfo=SchoolInfoModel [schoolType=null, schoolName=null, specialty=computer], hobbyList=[program, music], map={key1=value1, key2=value2}, phoneNumber=PhoneNumberModel [areaCode=010, phoneNumber=12345678], date=Sun Mar 18 16:48:48 CST 2012, state=锁定]

类型转换如图所示:

a633b31a42f0224c9bf66cd3cc886e04__2


四、注册PropertyEditor

1、使用WebDataBinder进行控制器级别注册PropertyEditor(控制器独享)

如“【三、示例】”中所使用的方式,使用WebDataBinder注册控制器级别的PropertyEditor,这种方式注册的PropertyEditor只对当前控制器独享,即其他的控制器不会自动注册这个PropertyEditor,如果需要还需要再注册一下。

2、使用WebBindingInitializer批量注册PropertyEditor

如果想在多个控制器同时注册多个相同的PropertyEditor时,可以考虑使用WebBindingInitializer。

示例:

(1、实现WebBindingInitializer

  1 package cn.javass.chapter4.web.controller.support.initializer;
  2 //省略import
  3 public class MyWebBindingInitializer implements WebBindingInitializer {
  4     @Override
  5     public void initBinder(WebDataBinder binder, WebRequest request) {
  6         //注册自定义的属性编辑器
  7         //1、日期
  8         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  9         CustomDateEditor dateEditor = new CustomDateEditor(df, true);
 10         //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
 11         binder.registerCustomEditor(Date.class, dateEditor);
 12         //自定义的电话号码编辑器
 13         binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
 14     }
 15 }

通过实现WebBindingInitializer并通过binder注册多个PropertyEditor。

(2、修改【三、示例】中的DataBinderTestController,注释掉initBinder方法;

(3、修改chapter4-servlet.xml配置文件:

  1 <!-- 注册WebBindingInitializer实现 -->
  2 <bean id="myWebBindingInitializer" class="cn.javass.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>
  3 <bean name="/dataBind" class="cn.javass.chapter4.web.controller.DataBinderTestController">
  4     <!-- 注入WebBindingInitializer实现 -->
  5     <property name="webBindingInitializer" ref="myWebBindingInitializer"/>
  6 </bean>


(4、尝试访问“【三、示例】”中的测试URL即可成功。

使用WebBindingInitializer的好处是当你需要在多个控制器中需要同时使用多个相同的PropertyEditor可以在WebBindingInitializer实现中注册,这样只需要在控制器中注入WebBindingInitializer即可注入多个PropertyEditor。

3、全局级别注册PropertyEditor(全局共享)

只需要将我们自定义的PropertyEditor放在和你的模型类同包下即可,且你的Editor命名规则必须是“模型类名Editor”,这样Spring会自动使用标准JavaBean架构进行自动识别,如图所示:

a633b31a42f0224c9bf66cd3cc886e04__2

此时我们把“DataBinderTestController”的“binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());”注释掉,再尝试访问“【三、示例】”中的测试URL即可成功。

这种方式不仅仅在使用Spring时可用,在标准的JavaBean等环境都是可用的,可以认为是全局共享的(不仅仅是Spring环境)。

PropertyEditor被限制为只能String<——>Object之间转换,不能Object<——>Object,Spring3提供了更强大的类型转换(TypeConversion)支持,它可以在任意对象之间进行类型转换,不仅仅是String<——>Object。


4.16.2、数据验证

1、数据绑定失败:比如需要数字却输入了字母;

2、数据不合法:可以认为是业务错误,通过自定义验证器验证,如用户名长度必须在5-20之间,我们却输入了100个字符等;

3、错误对象:当我们数据绑定失败或验证失败后,错误信息存放的对象,我们叫错误对象,在spring Web MVC中Errors是具体的代表者;线程不安全对象;

4、错误消息:是硬编码,还是可配置?实际工作应该使用配置方式,我们只是把错误码(errorCode)放入错误对象,在展示时读取相应的错误消息配置文件来获取要显示的错误消息(errorMessage);

4.16.2.1、验证流程

a633b31a42f0224c9bf66cd3cc886e04__2

1、首先进行数据绑定验证,如果验证失败会通过MessageCodesResolver生成错误码放入Errors错误对象;

2、数据不合法验证,通过自定义的验证器验证,如果失败需要手动将错误码放入Errors错误对象;

4.16.2.2、错误对象和错误消息

错误对象的代表者是Errors接口,并且提供了几个实现者,在Spring Web MVC中我们使用的是如下实现:


a633b31a42f0224c9bf66cd3cc886e04__2

相关的错误方法如下:

Errors存储和暴露关于数据绑定错误和验证错误相关信息的接口,提供了相关存储和获取错误消息的方法:

 

  1 package org.springframework.validation;
  2 public interface Errors {
  3   //=========================全局错误消息(验证/绑定对象全局的)=============================
  4   //注册一个全局的错误码()
  5   void reject(String errorCode);
  6   //注册一个全局的错误码,当根据errorCode没有找到相应错误消息时,使用defaultMessage作为错误消息
  7   void reject(String errorCode, String defaultMessage);
  8   //注册一个全局的错误码,当根据errorCode没有找到相应错误消息时(带错误参数的),使用defaultMessage作为错误消息
  9   void reject(String errorCode, Object[] errorArgs, String defaultMessage);
 10   //=========================全局错误消息(验证/绑定整个对象的)=============================
 11   //=========================局部错误消息(验证/绑定对象字段的)=============================
 12   //注册一个对象字段的错误码,field指定验证失败的字段名
 13   void rejectValue(String field, String errorCode);
 14   void rejectValue(String field, String errorCode, String defaultMessage);
 15   void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage);
 16   //=========================局部错误消息(验证/绑定对象字段的)=============================
 17   boolean hasErrors();      ////是否有错误
 18   boolean hasGlobalErrors(); //是否有全局错误
 19   boolean hasFieldErrors();  //是否有字段错误
 20   Object getFieldValue(String field); //返回当前验证通过的值,或验证失败时失败的值;
 21 }

getFieldValue:可以得到验证失败的失败值,这是其他Web层框架很少支持的,这样就可以给用户展示出错时的值(而不是空或其他的默认值等)。

BindingResult代表数据绑定的结果,继承了Errors接口。

BindException代表数据绑定的异常,它继承Exception,并实现了BindingResult,这是内部使用的错误对象。

示例:

(1、控制器

  1 package cn.javass.chapter4.web.controller;
  2 //省略import
  3 public class ErrorController extends AbstractCommandController {
  4        public ErrorController() {
  5               setCommandClass(DataBinderTestModel.class);
  6               setCommandName("command");
  7        }
  8        @Override
  9        protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
 10               //表示用户名不为空
 11               errors.reject("username.not.empty");
 12               //带有默认错误消息
 13               errors.reject("username.not.empty1", "用户名不能为空1");
 14               //带有参数和默认错误消息        
 15               errors.reject("username.length.error", new Object[]{5, 10});
 16 
 17               //得到错误相关的模型数据
 18               Map model = errors.getModel();
 19               return new ModelAndView("bindAndValidate/error", model);
 20        }
 21 }



errors.reject(“username.not.empty”)注册全局错误码“username.not.empty”,我们必须提供messageSource来提供错误码“username.not.empty”对应的错误信息(如果没有会抛出NoSuchMessageException异常);

errors.reject(“username.not.empty1″, “用户名不能为空1″)注册全局错误码“username.not.empty1”,如果从messageSource没没有找到错误码“username.not.empty1”对应的错误信息,则将显示默认消息“用户名不能为空1”;

errors.reject(“username.length.error”, new Object[]{5, 10})错误码为“username.length.error”,而且错误信息需要两个参数,如我们在我们的配置文件中定义“用户名长度不合法,长度必须在{0}到{1}之间”,则实际的错误消息为“用户名长度不合法,长度必须在5到10之间”

errors.getModel()当有错误信息时,一定将errors.getModel()放入我们要返回的ModelAndView中,以便使用里边的错误对象来显示错误信息。

(2、spring配置文件chapter4-servlet.xml

  1 <bean id="messageSource"
  2        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  3     <property name="basename" value="classpath:messages"/>
  4     <property name="fileEncodings" value="utf-8"/>
  5     <property name="cacheSeconds" value="120"/>
  6 </bean>
  7 
  8 <bean name="/error" class="cn.javass.chapter4.web.controller.ErrorController"/>

messageSource用于获取错误码对应的错误消息的,而且bean名字默认必须是messageSource。

messages.properties(需要执行NativeToAscii)

  1 username.not.empty=用户名不能为空
  2 username.length.error=用户名长度不合法,长度必须在{0}到{1}之间

3、视图页面(WEB-INF/jsp/bindAndValidate/error.jsp

  1 <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
  2 <!-- 表单的默认命令对象名为command -->
  3 <form:form commandName="command">
  4     <form:errors path="*"></form:errors>
  5 </form:form>



form标签库:此处我们使用了spring的form标签库;

<form:form commandName=”command”>:表示我们的表单标签,commandName表示绑定的命令对象名字,默认为command;

<form:errors path=”*”></form:errors>表示显示错误信息的标签,如果path为“*”表示显示所有错误信息。

接下来我们来看一下 数据绑定失败和数据不合法时,如何处理。

4.16.2.3、数据绑定失败

如我们的DataBinderTestModel类:

bool:boolean类型,此时如果我们前台传入非兼容的数据,则会数据绑定失败;

date:Date类型,此时如果我们前台传入非兼容的数据,同样会数据绑定失败;

phoneNumber:自定义的PhoneNumberModel类型,如果如果我们前台传入非兼容的数据,同样会数据绑定失败。

示例:

(1、控制器,DataBinderErrorTestController


  1 package cn.javass.chapter4.web.controller;
  2 //省略import
  3 public class DataBinderErrorTestController extends SimpleFormController {
  4        public DataBinderErrorTestController() {
  5               setCommandClass(DataBinderTestModel.class);
  6               setCommandName("dataBinderTest");
  7        }
  8        @Override
  9        protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception {
 10               //如果表单提交有任何错误都会再回到表单展示页面
 11               System.out.println(errors);
 12               return super.showForm(request, response, errors);
 13        }
 14        @Override
 15        protected void doSubmitAction(Object command) throws Exception {
 16               System.out.println(command); //表单提交成功(数据绑定成功)进行功能处理
 17     }
 18        @Override
 19        protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
 20               super.initBinder(request, binder);
 21               //注册自定义的属性编辑器
 22               //1、日期
 23               DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 24               CustomDateEditor dateEditor = new CustomDateEditor(df, true);
 25               //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
 26               binder.registerCustomEditor(Date.class, dateEditor);
 27 
 28               //自定义的电话号码编辑器
 29               binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
 30        }
 31 }

此处我们使用SimpleFormController;

showForm展示表单,当提交表单有任何数据绑定错误会再回到该方法进行表单输入(在此处我们打印错误对象);

doSubmitAction表单提交成功,只要当表单的数据到命令对象绑定成功时,才会执行;

(2、spring配置文件chapter4-servlet.xml

  1 <bean name="/dataBindError"
  2 class="cn.javass.chapter4.web.controller.DataBinderErrorTestController">
  3    <property name="formView" value="bindAndValidate/input"/>
  4    <property name="successView" value="bindAndValidate/success"/>
  5 </bean>

3、视图页面(WEB-INF/jsp/bindAndValidate/ input.jsp

  1 <%@ page language="java" contentType="text/html; charset=UTF-8"   pageEncoding="UTF-8"%>
  2 <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
  3 <!-- 表单的命令对象名为dataBinderTest -->
  4 <form:form commandName="dataBinderTest">
  5     <form:errors path="*" cssStyle="color:red"></form:errors>
  6     bool:<form:input path="bool"/>
  7     phoneNumber:<form:input path="phoneNumber"/>
  8     date:<form:input path="date"/>
  9     <input type="submit" value="提交"/>
 10 </form:form>

此处一定要使用form标签库,借此我们可以看到它的强大支持(别的大部分Web框架所不具备的,展示用户验证失败的数据)。

<form:form commandName=“dataBinderTest”>:指定命令对象为dataBinderTest,默认command;

<form:errors path=“*” cssStyle=“color:red”></form:errors>:显示错误消息,当提交表单有错误时展示错误消息(数据绑定错误/数据不合法);

<form:input path=“bool”/>:等价于(<input type=’text’>),但会从命令对象中取出bool属性进行填充value属性,或如果表单提交有错误会从错误对象取出之前的错误数据(而非空或默认值);

<input type=“submit” value=提交/>:spring没有提供相应的提交按钮,因此需要使用html的。

(4、测试

在地址栏输入如下地址:http://localhost:9080/springmvc-chapter4/dataBindError

image

全部是错误数据,即不能绑定到我们的命令对象;

当提交表单后,我们又回到表单输入页面,而且输出了一堆错误信息

a633b31a42f0224c9bf66cd3cc886e04__2

1、错误消息不可读;

2、表单元素可以显示之前的错误的数据,而不是默认值/空;

(5、问题

这里最大的问题是不可读的错误消息,如何让这些错误消息可读呢?

首先我们看我们的showForm方法里输出的“errors”错误对象信息:


  1 org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
  2 
  3 Field error in object 'dataBinderTest' on field 'bool': rejected value [www]; codes [typeMismatch.dataBinderTest.bool,typeMismatch.bool,typeMismatch.boolean,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.bool,bool]; arguments []; default message [bool]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'bool'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [www]]
  4 
  5 Field error in object 'dataBinderTest' on field 'date': rejected value [123]; codes [typeMismatch.dataBinderTest.date,typeMismatch.date,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.date,date]; arguments []; default message [date]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'date'; nested exception is java.lang.IllegalArgumentException: Could not parse date: Unparseable date: "123"]
  6 
  7 Field error in object 'dataBinderTest' on field 'phoneNumber': rejected value [123]; codes [typeMismatch.dataBinderTest.phoneNumber,typeMismatch.phoneNumber,typeMismatch.cn.javass.chapter4.model.PhoneNumberModel,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'cn.javass.chapter4.model.PhoneNumberModel' for property 'phoneNumber'; nested exception is java.lang.IllegalArgumentException: 类型转换失败,需要格式[010-12345678],但格式是[123]]


数据绑定失败(类型不匹配)会自动生成如下错误码(错误码对应的错误消息按照如下顺序依次查找):

1、typeMismatch.命令对象名.属性名

2、typeMismatch.属性名

3、typeMismatch.属性全限定类名(包名.类名)

4、typeMismatch

⊙内部使用MessageCodesResolver解析数据绑定错误到错误码,默认DefaultMessageCodesResolver,因此想要详细了解如何解析请看其javadoc;

⊙建议使用第1个进行错误码的配置。

因此修改我们的messages.properties添加如下错误消息(需要执行NativeToAscii):

  1 typeMismatch.dataBinderTest.date=您输入的数据格式错误,请重新输入(格式:2012-03-19 22:17:17)
  2 #typeMismatch.date=2
  3 #typeMismatch.java.util.Date=3
  4 #typeMismatch=4

a633b31a42f0224c9bf66cd3cc886e04__2


到此,数据绑定错误我们介绍完了,接下来我们再看一下数据不合法错误。

4.16.2.4、数据不合法

1、比如用户名长度必须在5-20之间,而且必须以字母开头,可包含字母、数字、下划线;

2、比如注册用户时 用户名已经存在或邮箱已经存在等;

3、比如去一些论坛经常会发现,您发的帖子中包含×××屏蔽关键字等。

还有很多数据不合法的场景,在此就不罗列了,对于数据不合法,Spring Web MVC提供了两种验证方式:

◆编程式验证器验证

◆声明式验证

先从编程式验证器开始吧。

4.16.2.4.1、编程式验证器

一、验证器接口

  1 package org.springframework.validation;
  2 public interface Validator {
  3 boolean supports(Class<?> clazz);
  4 void validate(Object target, Errors errors);
  5 }


Validator接口:验证器,编程实现数据验证的接口;

supports方法:当前验证器是否支持指定的clazz验证,如果支持返回true即可;

validate方法:验证的具体方法,target参数表示要验证的目标对象(如命令对象),errors表示验证出错后存放错误信息的错误对象。

示例:

(1、验证器实现

  1 package cn.javass.chapter4.web.controller.support.validator;
  2 //省略import
  3 public class UserModelValidator implements Validator {
  4     private static final Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Z]\\w{4,19}");
  5     private static final Pattern PASSWORD_PATTERN = Pattern.compile("[a-zA-Z0-9]{5,20}");
  6     private static final Set<String> FORBINDDDEN_WORD_SET = new HashSet<String>();
  7     static {
  8        FORBINDDDEN_WORD_SET.add("fuc k"); //删掉空格
  9        FORBINDDDEN_WORD_SET.add("admin");
 10     }
 11     @Override
 12     public boolean supports(Class<?> clazz) {
 13        return UserModel.class == clazz;//表示只对UserModel类型的目标对象实施验证
 14     }
 15     @Override
 16     public void validate(Object target, Errors errors) {
 17        //这个表示如果目标对象的username属性为空,则表示错误(简化我们手工判断是否为空)
 18        ValidationUtils.rejectIfEmpty(errors, "username", "username.not.empty");
 19 
 20        UserModel user = (UserModel) target;
 21 
 22        if(!USERNAME_PATTERN.matcher(user.getUsername()).matches()) {
 23            errors.rejectValue("username", "username.not.illegal");//如果用户名不合法
 24        }
 25 
 26        for(String forbiddenWord : FORBINDDDEN_WORD_SET) {
 27            if(user.getUsername().contains(forbiddenWord)) {
 28               errors.rejectValue("username", "username.forbidden", new Object[]{forbiddenWord}, "您的用户名包含非法关键词");//用户名包含屏蔽关键字
 29               break;
 30            }
 31        }
 32        if(!PASSWORD_PATTERN.matcher(user.getPassword()).matches()) {
 33            errors.rejectValue("password","password.not.illegal", "密码不合法");//密码不合法
 34        }
 35     }
 36 }


supports方法:表示只对UserModel类型的对象验证;

validate方法:数据验证的具体方法,有如下几个验证:

1、用户名不合法(长度5-20,以字母开头,随后可以是字母、数字、下划线)

USERNAME_PATTERN.matcher(user.getUsername()).matches() //使用正则表达式验证

errors.rejectValue(“username”, “username.not.illegal”);//验证失败为username字段添加错误码

2、屏蔽关键词:即用户名中含有不合法的数据(如admin)

user.getUsername().contains(forbiddenWord) //用contains来判断我们的用户名中是否含有非法关键词

errors.rejectValue(“username”, “username.forbidden”, new Object[]{forbiddenWord}, “您的用户名包含非法关键词”);//验证失败为username字段添加错误码(参数为当前屏蔽关键词)(默认消息为”您的用户名包含非法关键词”)

3、密码不合法

在此就不罗列代码了;

4、ValidationUtils

ValidationUtils.rejectIfEmpty(errors, “username”, “username.not.empty”);

表示如果目标对象的username属性数据为空,则添加它的错误码;

内部通过(value == null || !StringUtils.hasLength(value.toString()))实现判断value是否为空,从而简化代码。

(2、spring配置文件chapter4-servlet.xml

  1 <bean id="userModelValidator"
  2 class="cn.javass.chapter4.web.controller.support.validator.UserModelValidator"/>
  3 <bean name="/validator"
  4 class="cn.javass.chapter4.web.controller.RegisterSimpleFormController">
  5     <property name="formView" value="registerAndValidator"/>
  6     <property name="successView" value="redirect:/success"/>
  7     <property name="validator" ref="userModelValidator"/>
  8 </bean>

此处使用了我们第4.9节创建的RegisterSimpleFormController。

(3、错误码配置(messages.properties),需要执行NativeToAscii


  1 username.not.empty=用户名不能为空
  2 username.not.illegal=用户名错误,必须以字母开头,只能出现字母、数字、下划线,并且长度在5-20之间
  3 username.forbidden=用户名中包含非法关键词【{0}】
  4 password.not.illegal=密码长度必须在5-20之间
  5 

(4、视图页面(/WEB-INF/jsp/registerAndValidator.jsp

  1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2 <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
  3 <form:form commandName="user">
  4 
  5 <form:errors path="*" cssStyle="color:red"></form:errors>
  6 
  7 username:<form:input path="username"/>
  8 <form:errors path="username" cssStyle="color:red"></form:errors>
  9 
 10 
 11 password:<form:password path="password"/>
 12 <form:errors path="password" cssStyle="color:red"></form:errors>
 13 
 14 <input type="submit" value="注册"/>
 15 </form:form>
 16 


form:errors path=“username”表示只显示username字段的错误信息;

5、测试

地址:http://localhost:9080/springmvc-chapter4/validator


image

当我们输入错误的数据后,会报错(form:errors path=“*”显示所有错误信息,而form:errors path=“username”只显示该字段相关的)。

问题:

如MultiActionController控制器相关方法没有提供给我们errors对象(Errors),我们应该怎么进行错误处理呢?

此处给大家一个思路,errors本质就是一个Errors接口实现,而且在页面要读取相关的错误对象,该错误对象应该存放在模型对象里边,因此我们可以自己创建个errors对象并将其添加到模型对象中即可。

此处我们复制4.15节的UserController类为UserAndValidatorController,并修改它的create(新增)方法添加如下代码片段:

  1 BindException errors = new BindException(user, getCommandName(user));
  2 //如果用户名为空
  3 if(!StringUtils.hasLength(user.getUsername())) {
  4     errors.rejectValue("username", "username.not.empty");
  5 }
  6 if(errors.hasErrors()) {
  7     return new ModelAndView(getCreateView()).addAllObjects(errors.getModel());
  8 }


new BindException(user, getCommandName(user))使用当前的命令对象,和命令对象的名字创建了一个BindException作为errors;

StringUtils.hasLength(user.getUsername())如果用户名为空就是用errors.rejectValue(“username”, “username.not.empty”);注入错误码;

errors.hasErrors()表示如果有错误就返回到新增页面并显示错误消息;

ModelAndView(getCreateView()).addAllObjects(errors.getModel())此处一定把errors对象的模型数据放在当前的ModelAndView中,作为当前请求的模型数据返回。

在浏览器地址栏输入:http://localhost:9080/springmvc-chapter4/userAndValidator/create 到新增页面

image

用户名什么都不输入,提交后又返回到新增页面 而且显示了错误消息说明我们的想法是正确的。

4.16.2.4.2、声明式验证器

从Spring3开始支持JSR-303验证框架,支持XML风格和注解风格的验证,目前在@RequestMapping时才能使用,也就是说基于Controller接口的实现不能使用该方式(但可以使用编程式验证,有需要的可以参考hibernatevalidator实现),我们将在第七章详细介绍。

到此Spring2风格的控制器我们就介绍完了,以上控制器从spring3.0开始已经不推荐使用了(但考虑到还有部分公司使用这些@Deprecated类,在此也介绍了一下),而是使用注解控制器实现(@Controller和@RequestMapping)。