本文转载自:http://www.javaxxz.com/thread-359436-1-1.html

前言
本博文主要讲解的知识点如下:

校验器
统一处理异常
RESTful
拦截器

Validation
在我们的Struts2中,我们是继承ActionSupport来实现校验的...它有两种方式来实现校验的功能

手写代码
XML配置
  
   这两种方式也是可以特定处理方法或者整个Action的
  

而SpringMVC使用JSR-303(javaEE6规范的一部分)校验规范,springmvc使用的是Hibernate Validator(和Hibernate的ORM无关)
快速入门
导入jar包

配置校验器

  1. [code]


  2.    

  3.     <!-- 校验器 -->

  4.     <bean id="validator"

  5. class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">

  6.         <!-- 校验器 -->

  7.         <property name="providerClass" value="org.hibernate.validator.HibernateValidator" />

  8.         <!-- 指定校验使用的资源文件,如果不指定则默认使用classpath下的ValidationMessages.properties -->

  9.         <property name="validationMessageSource" ref="messageSource" />

  10.     </bean>


复制代码

[/code]

错误信息的校验文件配置

  1. [code]

  2.     <!-- 校验错误信息配置文件 -->

  3.     <bean id="messageSource"

  4. class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

  5.         <!-- 资源文件名 -->

  6.         <property name="basenames">

  7.             <list>

  8.                 <value>classpath:CustomValidationMessages</value>

  9.             </list>

  10.         </property>

  11.         <!-- 资源文件编码格式 -->

  12.         <property name="fileEncodings" value="utf-8" />

  13.         <!-- 对资源文件内容缓存时间,单位秒 -->

  14.         <property name="cacheSeconds" value="120" />

  15.     </bean>

复制代码

[/code]

添加到自定义参数绑定的WebBindingInitializer中

  1. [code]

  2.     <!-- 自定义webBinder -->

  3.     <bean id="customBinder"

  4. class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">

  5.         <!-- 配置validator -->

  6.         <property name="validator" ref="validator" />

  7.     </bean>

复制代码

[/code]

最终添加到适配器中

  1. [code]

  2.     <!-- 注解适配器 -->

  3.     <bean

  4. class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">

  5.         <!-- 在webBindingInitializer中注入自定义属性编辑器、自定义转换器 -->

  6.         <property name="webBindingInitializer" ref="customBinder"></property>

  7.     </bean>

复制代码

[/code]

创建CustomValidationMessages配置文件

定义规则

  1. [code]package entity;


  2. import javax.validation.constraints.NotNull;

  3. import javax.validation.constraints.Size;

  4. import java.util.Date;


  5. public class Items {

  6.     private Integer id;


  7.     //商品名称的长度请限制在1到30个字符

  8.     @Size(min=1,max=30,message="{items.name.length.error}")

  9.     private String name;


  10.     private Float price;


  11.     private String pic;


  12.     //请输入商品生产日期

  13.     @NotNull(message="{items.createtime.is.notnull}")

  14.     private Date createtime;


  15.     private String detail;


  16.     public Integer getId() {

  17.         return id;

  18.     }


  19.     public void setId(Integer id) {

  20.         this.id = id;

  21.     }


  22.     public String getName() {

  23.         return name;

  24.     }


  25.     public void setName(String name) {

  26.         this.name = name == null ? null : name.trim();

  27.     }


  28.     public Float getPrice() {

  29.         return price;

  30.     }


  31.     public void setPrice(Float price) {

  32.         this.price = price;

  33.     }


  34.     public String getPic() {

  35.         return pic;

  36.     }


  37.     public void setPic(String pic) {

  38.         this.pic = pic == null ? null : pic.trim();

  39.     }


  40.     public Date getCreatetime() {

  41.         return createtime;

  42.     }


  43.     public void setCreatetime(Date createtime) {

  44.         this.createtime = createtime;

  45.     }


  46.     public String getDetail() {

  47.         return detail;

  48.     }


  49.     public void setDetail(String detail) {

  50.         this.detail = detail == null ? null : detail.trim();

  51.     }

  52. }

复制代码

[/code]

测试:

  1. [code]



  2. <%--

  3.   Created by IntelliJ IDEA.

  4.   User: ozc

  5.   Date: 2017/8/11

  6.   Time: 9:56

  7.   To change this template use File | Settings | File Templates.

  8. --%>

  9. <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  10. <html>

  11. <head>

  12.     <title>测试文件上传</title>

  13. </head>

  14. <body>



  15. <form action="${pageContext.request.contextPath}/validation.action" method="post" >

  16.     名称:<input type="text" name="name">

  17.     日期:<input type="text" name="createtime">

  18.     <input type="submit" value="submit">

  19. </form>


  20. </body>

  21. </html>



复制代码

[/code]

Controller需要在校验的参数上添加@Validation注解...拿到BindingResult对象...

  1. [code]

  2.     @RequestMapping("/validation")

  3.     public void validation(@Validated Items items, BindingResult bindingResult) {


  4.         List<ObjectError> allErrors = bindingResult.getAllErrors();

  5.         for (ObjectError allError : allErrors) {

  6.             System.out.println(allError.getDefaultMessage());

  7.         }


  8.     }

复制代码

[/code]

由于我在测试的时候,已经把日期转换器关掉了,因此提示了字符串不能转换成日期,但是名称的校验已经是出来了...



分组校验
分组校验其实就是为了我们的校验更加灵活,有的时候,我们 并不需要把我们当前配置的属性都进行校验,而需要的是当前的方法仅仅校验某些的属性。那么此时,我们就可以用到分组校验了...
步骤:

定义分组的接口【主要是标识】
定于校验规则属于哪一各组
在Controller方法中定义使用校验分组





统一异常处理
在我们之前SSH,使用Struts2的时候也配置过统一处理异常...
当时候是这么干的:

在service层中自定义异常
在action层也自定义异常
对于Dao层的异常我们先不管【因为我们管不着,dao层的异常太致命了】
service层抛出异常,Action把service层的异常接住,通过service抛出的异常来判断是否让请求通过
如果不通过,那么接着抛出Action异常
在Struts的配置文件中定义全局视图,页面显示错误信息

详情可看:http://blog.youkuaiyun.com/hon_3y/article/details/72772559
那么我们这次的统一处理异常的方案是什么呢????
我们知道Java中的异常可以分为两类

编译时期异常
运行期异常

对于运行期异常我们是无法掌控的,只能通过代码质量、在系统测试时详细测试等排除运行时异常
而对于编译时期的异常,我们可以在代码手动处理异常可以try/catch捕获,可以向上抛出。
我们可以换个思路,自定义一个模块化的异常信息,比如:商品类别的异常

  1. [code]

  2. public class CustomException extends Exception {

  3.    

  4.     //异常信息

  5.     private String message;

  6.    

  7.     public CustomException(String message){

  8.         super(message);

  9.         this.message = message;

  10.         

  11.     }


  12.     public String getMessage() {

  13.         return message;

  14.     }


  15.     public void setMessage(String message) {

  16.         this.message = message;

  17.     }

  18.    

  19.    


  20. }

复制代码

[/code]

我们在查看Spring源码的时候发现:前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常, 在系统中自定义统一的异常处理器,写系统自己的异常处理代码。。


我们也可以学着点,定义一个统一的处理器类来处理异常...
定义统一异常处理器类

  1. [code]

  2. public class CustomExceptionResolver implements HandlerExceptionResolver  {


  3.     //前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法

  4.     //handler最终要执行的Handler,它的真实身份是HandlerMethod

  5.     //Exception ex就是接收到异常信息

  6.     @Override

  7.     public ModelAndView resolveException(HttpServletRequest request,

  8.             HttpServletResponse response, Object handler, Exception ex) {

  9.         //输出异常

  10.         ex.printStackTrace();

  11.         

  12.         //统一异常处理代码

  13.         //针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面展示

  14.         //异常信息

  15.         String message = null;

  16.         CustomException customException = null;

  17.         //如果ex是系统 自定义的异常,直接取出异常信息

  18.         if(ex instanceof CustomException){

  19.             customException = (CustomException)ex;

  20.         }else{

  21.             //针对非CustomException异常,对这类重新构造成一个CustomException,异常信息为“未知错误”

  22.             customException = new CustomException("未知错误");

  23.         }


  24.         //错误 信息

  25.         message = customException.getMessage();

  26.         

  27.         request.setAttribute("message", message);


  28.         

  29.         try {

  30.             //转向到错误 页面

  31.             request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);

  32.         } catch (ServletException e) {

  33.             // TODO Auto-generated catch block

  34.             e.printStackTrace();

  35.         } catch (IOException e) {

  36.             // TODO Auto-generated catch block

  37.             e.printStackTrace();

  38.         }

  39.         

  40.         return new ModelAndView();

  41.     }


  42. }

复制代码

[/code]

配置统一异常处理器

  1. [code]    <!-- 定义统一异常处理器 -->

  2.     <bean class="cn.itcast.ssm.exception.CustomExceptionResolver"></bean>

复制代码

[/code]


RESTful支持
我们在学习webservice的时候可能就听过RESTful这么一个名词,当时候与SOAP进行对比的...那么RESTful究竟是什么东东呢???
RESTful(Representational State Transfer)软件开发理念,RESTful对http进行非常好的诠释
如果一个架构支持RESTful,那么就称它为RESTful架构...
以下的文章供我们了解:
http://www.ruanyifeng.com/blog/2011/09/restful
综合上面的解释,我们总结一下什么是RESTful架构:

  (1)每一个URI代表一种资源;

  (2)客户端和服务器之间,传递这种资源的某种表现层;
  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"

关于RESTful幂等性的理解:http://www.oschina.net/translate/put-or-post
简单来说,如果对象在请求的过程中会发生变化(以Java为例子,属性被修改了),那么此是非幂等的。多次重复请求,结果还是不变的话,那么就是幂等的。
PUT用于幂等请求,因此在更新的时候把所有的属性都写完整,那么多次请求后,我们其他属性是不会变的
在上边的文章中,幂等被翻译成“状态统一性”。这就更好地理解了。
其实一般的架构并不能完全支持RESTful的,因此,只要我们的系统支持RESTful的某些功能,我们一般就称作为支持RESTful架构...
url的RESTful实现
非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是简洁的:http:// localhost:8080/items/editItems/1
更改DispatcherServlet的配置
从上面我们可以发现,url并没有.action后缀的,因此我们要修改核心分配器的配置

  1. [code]

  2.     <!-- restful的配置 -->

  3.     <servlet>

  4.         <servlet-name>springmvc_rest</servlet-name>

  5.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  6.         <!-- 加载springmvc配置 -->

  7.         <init-param>

  8.             <param-name>contextConfigLocation</param-name>

  9.             <!-- 配置文件的地址 如果不配置contextConfigLocation, 默认查找的配置文件名称classpath下的:servlet名称+"-serlvet.xml"即:springmvc-serlvet.xml -->

  10.             <param-value>classpath:spring/springmvc.xml</param-value>

  11.         </init-param>


  12.     </servlet>

  13.     <servlet-mapping>

  14.         <servlet-name>springmvc_rest</servlet-name>

  15.         <!-- rest方式配置为/ -->

  16.         <url-pattern>/</url-pattern>

  17.     </servlet-mapping>

复制代码

[/code]

在Controller上使用PathVariable注解来绑定对应的参数

  1. [code]

  2.     //根据商品id查看商品信息rest接口

  3.     //@RequestMapping中指定restful方式的url中的参数,参数需要用{}包起来

  4.     //@PathVariable将url中的{}包起参数和形参进行绑定

  5.     @RequestMapping("/viewItems/{id}")

  6.     public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{

  7.         //调用 service查询商品信息

  8.         ItemsCustom itemsCustom = itemsService.findItemsById(id);

  9.         

  10.         return itemsCustom;

  11.         

  12.     }

复制代码

[/code]

当DispatcherServlet拦截/开头的所有请求,对静态资源的访问就报错:我们需要配置对静态资源的解析

  1. [code]

  2.     <!-- 静态资源 解析 -->

  3.     <mvc:resources location="/js/" mapping="/js/**" />

  4.     <mvc:resources location="/img/" mapping="/img/**" />

复制代码

[/code]
  1. /**

复制代码

就表示不管有多少层,都对其进行解析,
  1. /*

复制代码

代表的是当前层的所有资源..


SpringMVC拦截器
在Struts2中拦截器就是我们当时的核心,原来在SpringMVC中也是有拦截器的
用户请求到DispatherServlet中,DispatherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截的链儿(多个拦截),springmvc中的拦截器是通过HandlerMapping发起的。
实现拦截器的接口:

  1. [code]

  2. public class HandlerInterceptor1 implements HandlerInterceptor {


  3.     //在执行handler之前来执行的

  4.     //用于用户认证校验、用户权限校验

  5.     @Override

  6.     public boolean preHandle(HttpServletRequest request,

  7.             HttpServletResponse response, Object handler) throws Exception {

  8.         

  9.         System.out.println("HandlerInterceptor1...preHandle");

  10.         

  11.         //如果返回false表示拦截不继续执行handler,如果返回true表示放行

  12.         return false;

  13.     }

  14.     //在执行handler返回modelAndView之前来执行

  15.     //如果需要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手

  16.     @Override

  17.     public void postHandle(HttpServletRequest request,

  18.             HttpServletResponse response, Object handler,

  19.             ModelAndView modelAndView) throws Exception {

  20.         System.out.println("HandlerInterceptor1...postHandle");

  21.         

  22.     }

  23.     //执行handler之后执行此方法

  24.     //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长

  25.     //实现 系统 统一日志记录

  26.     @Override

  27.     public void afterCompletion(HttpServletRequest request,

  28.             HttpServletResponse response, Object handler, Exception ex)

  29.             throws Exception {

  30.         System.out.println("HandlerInterceptor1...afterCompletion");

  31.     }


  32. }

复制代码

[/code]

配置拦截器

  1. [code]

  2.     <!--拦截器 -->

  3.     <mvc:interceptors>

  4.         <!--多个拦截器,顺序执行 -->

  5.         <!-- <mvc:interceptor>

  6. <mvc:mapping path="/**" />

  7. <bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor1"></bean>

  8. </mvc:interceptor>

  9. <mvc:interceptor>

  10. <mvc:mapping path="/**" />

  11. <bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor2"></bean>

  12. </mvc:interceptor> -->

  13.         

  14.         <mvc:interceptor>

  15.             <!-- /**可以拦截路径不管多少层 -->

  16.             <mvc:mapping path="/**" />

  17.             <bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean>

  18.         </mvc:interceptor>

  19.     </mvc:interceptors>

复制代码

[/code]

测试执行顺序
如果两个拦截器都放行

  1. [code]

  2. 测试结果:

  3. HandlerInterceptor1...preHandle

  4. HandlerInterceptor2...preHandle


  5. HandlerInterceptor2...postHandle

  6. HandlerInterceptor1...postHandle


  7. HandlerInterceptor2...afterCompletion

  8. HandlerInterceptor1...afterCompletion


  9. 总结:

  10. 执行preHandle是顺序执行。

  11. 执行postHandle、afterCompletion是倒序执行

复制代码

[/code]

1 号放行和2号不放行

  1. [code]

  2. 测试结果:

  3. HandlerInterceptor1...preHandle

  4. HandlerInterceptor2...preHandle

  5. HandlerInterceptor1...afterCompletion


  6. 总结:

  7. 如果preHandle不放行,postHandle、afterCompletion都不执行。

  8. 只要有一个拦截器不放行,controller不能执行完成

复制代码

[/code]

1 号不放行和2号不放行

  1. [code]

  2. 测试结果:

  3. HandlerInterceptor1...preHandle

  4. 总结:

  5. 只有前边的拦截器preHandle方法放行,下边的拦截器的preHandle才执行。

复制代码

[/code]

日志拦截器或异常拦截器要求

将日志拦截器或异常拦截器放在拦截器链儿中第一个位置,且preHandle方法放行

拦截器应用-身份认证
拦截器拦截

  1. [code]

  2. public class LoginInterceptor implements HandlerInterceptor {


  3.     //在执行handler之前来执行的

  4.     //用于用户认证校验、用户权限校验

  5.     @Override

  6.     public boolean preHandle(HttpServletRequest request,

  7.             HttpServletResponse response, Object handler) throws Exception {

  8.         

  9.         //得到请求的url

  10.         String url = request.getRequestURI();

  11.         

  12.         //判断是否是公开 地址

  13.         //实际开发中需要公开 地址配置在配置文件中

  14.         //...

  15.         if(url.indexOf("login.action")>=0){

  16.             //如果是公开 地址则放行

  17.             return true;

  18.         }

  19.         

  20.         //判断用户身份在session中是否存在

  21.         HttpSession session = request.getSession();

  22.         String usercode = (String) session.getAttribute("usercode");

  23.         //如果用户身份在session中存在放行

  24.         if(usercode!=null){

  25.             return true;

  26.         }

  27.         //执行到这里拦截,跳转到登陆页面,用户进行身份认证

  28.         request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);

  29.         

  30.         //如果返回false表示拦截不继续执行handler,如果返回true表示放行

  31.         return false;

  32.     }

  33.     //在执行handler返回modelAndView之前来执行

  34.     //如果需要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手

  35.     @Override

  36.     public void postHandle(HttpServletRequest request,

  37.             HttpServletResponse response, Object handler,

  38.             ModelAndView modelAndView) throws Exception {

  39.         System.out.println("HandlerInterceptor1...postHandle");

  40.         

  41.     }

  42.     //执行handler之后执行此方法

  43.     //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长

  44.     //实现 系统 统一日志记录

  45.     @Override

  46.     public void afterCompletion(HttpServletRequest request,

  47.             HttpServletResponse response, Object handler, Exception ex)

  48.             throws Exception {

  49.         System.out.println("HandlerInterceptor1...afterCompletion");

  50.     }


  51. }

复制代码

[/code]

Controller

  1. [code]

  2. @Controller

  3. public class LoginController {

  4.    

  5.    

  6.     //用户登陆提交方法

  7.     @RequestMapping("/login")

  8.     public String login(HttpSession session, String usercode,String password)throws Exception{

  9.         

  10.         //调用service校验用户账号和密码的正确性

  11.         //..

  12.         

  13.         //如果service校验通过,将用户身份记录到session

  14.         session.setAttribute("usercode", usercode);

  15.         //重定向到商品查询页面

  16.         return "redirect:/items/queryItems.action";

  17.     }

  18.    

  19.     //用户退出

  20.     @RequestMapping("/logout")

  21.     public String logout(HttpSession session)throws Exception{

  22.         

  23.         //session失效

  24.         session.invalidate();

  25.         //重定向到商品查询页面

  26.         return "redirect:/items/queryItems.action";

  27.         

  28.     }

  29.    


  30. }

复制代码

[/code]

总结

使用Spring的校验方式就是将要校验的属性前边加上注解声明。
**在Controller中的方法参数上加上@Validation注解。那么SpringMVC内部就会帮我们对其进行处理(创建对应的bean,加载配置文件)**
BindingResult可以拿到我们校验错误的提示
分组校验就是将让我们的校验更加灵活:某方法需要校验这个属性,而某方法不用校验该属性。我们就可以使用分组校验了。
对于处理异常,SpringMVC是用一个统一的异常处理器类的。实现了HandlerExceptionResolver接口。
对模块细分多个异常类,都交由我们的统一异常处理器类进行处理。
对于RESTful规范,我们可以使用SpringMVC简单地支持的。将SpringMVC的拦截.action改成是任意的。同时,如果是静态的资源文件,我们应该设置不拦截。
对于url上的参数,**我们可以使用@PathVariable将url中的{}包起参数和形参进行绑定**
SpringMVC的拦截器和Struts2的拦截器差不多。不过SpringMVC的拦截器配置起来比Struts2的要简单。
  
   至于他们的拦截器链的调用顺序,和Filter的是没有差别的。