Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 722|回复: 0

[默认分类] SpringMVC【校验器、统一处理异常、RESTful、拦截器】

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-3-18 22:43:38 | 显示全部楼层 |阅读模式
    前言
    本博文主要讲解的知识点如下:

    校验器
    统一处理异常
    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.csdn.net/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的是没有差别的。
      




    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-24 07:17 , Processed in 0.461375 second(s), 54 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表