zoukankan      html  css  js  c++  java
  • SpringBoot第七集:统一异常处理与整合JSR303校验(2020最新最易懂)

    SpringBoot第七集:统一异常处理与整合JSR303校验(2020最新最易懂)

    一.SpringBoot全局异常

      先讲下什么是全局异常处理器? 

      全局异常处理器就是把整个系统的异常统一自动处理,程序员可以做到不用写try... catch。SpringBoot内置有默认全局异常处理器。

      Spring Boot对异常的处理有一套默认的机制,BasicErrorController处理默认异常转发的或这error请求 :当应用中产生异常时,当从浏览器地址栏中访问应用接口时,SpringBoot会获取请求头中数据,如果请求头中的accept包含text/html信息,产生异常时,Spring Boot会通过ModelAndView模型对象来装载异常信息,并以HTML的格式返回反之,请求头中的accept不包含text/html时,Spring Boot则以JSON的格式返回异常信息

    例如:访问一个未知接口资源(或后台接口定义10/0的错误,响应的HTML结果如下)

    例如:利用Postman测试工具,访问未知资源测试:(可以尝试使用其他插件工具:使用Chrome插件Restlet Client

    1.全局异常处理机制源码解析

     BasicErrorController源码截取如下:

    @RequestMapping("${server.error.path:${error.path:/error}}")请求的异常页面地址为/error/下面的资源

     当没有自定义异常页面时,默认按下方源码执行构建HTML或JSON响应给前台。

     1 @Controller
     2 @RequestMapping("${server.error.path:${error.path:/error}}")
     3 public class BasicErrorController extends AbstractErrorController {
     4     /**
     5      * 错误信息处理器方法errorHtml,设置了请求头Accpet值类型,如果包含text/html,即执行该方法
     6      * @param request 请求对象
     7      * @param response    响应对象
     8      * @return
     9      *  MediaType.TEXT_HTML_VALUE的实际值就是一个字符串“text/html”
    10      */
    11     @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    12     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    13         // 获取状态码
    14         HttpStatus status = getStatus(request);
    15         Map<String, Object> model = Collections
    16                 .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
    17         // 响应状态码描述
    18         response.setStatus(status.value());
    19         // 创建视图模型对象
    20         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    21         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    22     }
    23 
    24     /**
    25      * 错误信息处理器方法error方法,设置了请求头Accpet值类型,即没有包含text/html执行该方法
    26      * @param request 请求对象
    27      * @param response    响应对象
    28      */
    29     @RequestMapping
    30     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    31         HttpStatus status = getStatus(request);
    32         if (status == HttpStatus.NO_CONTENT) {
    33             return new ResponseEntity<>(status);
    34         }
    35         Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
    36         return new ResponseEntity<>(body, status);
    37     }
    38     
    39 }

    我们可以自定义友好的异常页面。但必须是放在资源/error/目录下,资源目录存放默认地址可选
     src/main/resources/static/,src/main/resources/resources/,src/main/resources/public/,src/main/templates/

    说明:前三者是静态资源目录,页面我们使用模板引擎,因此如果需要自定义错误页面,那么需要放在src/main/templates/error目录下(当然所有的前提是,没有更改默认配置,SpringBoot默认加载其中的错误页面),且错误页面命名必须以状态码方式。SpringBoot默认错误视图解析器DefaultErrorViewResolver源码解析如下:

     1 public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
     2 
     3     private static final Map<Series, String> SERIES_VIEWS;
     4     
     5     // 静态初始化错误状态类型:4xx  或  5xx
     6     static {
     7         Map<Series, String> views = new EnumMap<>(Series.class);
     8         views.put(Series.CLIENT_ERROR, "4xx");
     9         views.put(Series.SERVER_ERROR, "5xx");
    10         SERIES_VIEWS = Collections.unmodifiableMap(views);
    11     }
    12         
    13 
    14     // 解析错误视图
    15     @Override
    16     public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
    17         // 获取错误状态码例如:404,转为字符串调用方法resolve(解析方法)
    18         ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
    19         if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
    20             modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
    21         }
    22         return modelAndView;
    23     }
    24     
    25     // 解析处理方法
    26     private ModelAndView resolve(String viewName, Map<String, Object> model) {
    27         // 拼接错误视图访问前缀:error/500
    28         String errorViewName = "error/" + viewName;
    29         TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
    30                 this.applicationContext);
    31         if (provider != null) {
    32             return new ModelAndView(errorViewName, model);
    33         }
    34         // 调用解析资源:传入error/500
    35         return resolveResource(errorViewName, model);
    36     }
    37 
    38     // 解析资源
    39     private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    40         for (String location : this.resourceProperties.getStaticLocations()) {
    41             try {
    42                 // 获取资解析
    43                 Resource resource = this.applicationContext.getResource(location);
    44                 // 创建解析文件为:error/500.html
    45                 resource = resource.createRelative(viewName + ".html");
    46                 if (resource.exists()) {
    47                     return new ModelAndView(new HtmlResourceView(resource), model);
    48                 }
    49             }
    50             catch (Exception ex) {
    51             }
    52         }
    53         return null;
    54     }
    55 }

       关于模板引擎的整合,参考第九集:整合JSP和模板引擎

     2.自定义异常页面

      1.在src/main/templates/error目录下新建错误页面:例如:404.html

      2.测试访问。

     1 <!DOCTYPE html>
     2 <html  xmlns:th="http://www.thymeleaf.org">  <!-- Thymeleaf模板约束 -->
     3 <head>
     4 <meta charset="UTF-8">
     5 <title>Insert title here</title>
     6 </head>
     7 <body>
     8     自定义404友好错误页面!<br>
     9     对不起,你访问的数据被外星人盗窃了……
    10 </body>
    11 </html>

     3.自定义异常信息

      除了可以可以自定义友好异常页面(HTML)外,我们也可以自定义异常处理信息,改变默认的客户端访问接口产生的异常信息。  

      由于工作中都是前后端分离开发模式,所以几乎没有直接返回资源页的需求,一般上都是返回固定的响应格式JSON,如respCoderespMsgdata等,前端通过判断respCode的值进行业务判断,是弹窗还是跳转页面。

    1. 编写自定义异常类,封装异常信息(便于JSON转换)
       1 @Data
       2 @NoArgsConstructor
       3 @AllArgsConstructor
       4 public class ExceptionResponseResult{
       5     @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
       6     private Date timestamp;// 时间
       7     private String respMsg;// 给用户看的描述信息
       8     private String message;// 实际错误异常信息
       9     private String exceptionName;// 实际错误异常名字
      10     private String path;// URI
      11 }
    2. 编写全局异常处理器
      a,编写一个全局异常处理器类
      b,在类上添加注解@ControllerAdvice
        @ControllerAdvice:作用:对所有控制器中,被@RequestMapping注解标注的方法,进行增强(也可以直接使用@RestControllerAdvice)
      c,自定义异常处理方法,并使用注解@ExceptionHandler(Throwable.class),@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR),@ResponseBody
        @ExceptionHandler(Throwable.class):异常处理器注解,通常配合@ControllerAdvice注解使用。作用是:对指定或满足的异常类型实施拦截处理
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR):用于指定响应状态码的,HttpStatus是Spring内置的一个状态码枚举类,内定了详细的状态码及描。
        @ResponseBody:作用:将响应的结果转为JSON信息。如果使用了@RestControllerAdvice则方法无需使用@ResponseBody注解
       1 // @ControllerAdvice
       2 @RestControllerAdvice    // 控制器类增强:可以对Controller中所有使用@RequestMapping注解的方法增强
       3 public class GlobalExceptionHandler {
       4 
       5     // 该注解是异常处理器注解,可以对指定异常类型处理,执行注解标注的方法(只要发生指定异常都会被拦截)
       6     @ExceptionHandler(Throwable.class)    
       7     // 该注解用于指定异常处理方法执行后响应页面的HTTP状态码,HttpStatus是Spring内置的一个状态码枚举类,内定了详细的状态码及描述,当前获取的是500
       8     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 响应500
       9     public Object exceptonResponse(Exception ex,HttpServletRequest request) {
      10         ExceptionResponseResult resultError = new ExceptionResponseResult();
      11         resultError.setTimestamp(new Date());// 设置异常发生时间
      12         
      13         resultError.setRespMsg("服务器刷新异常,请稍后。。。");// 用户看到的异常信息
      14         resultError.setMessage(ex.getMessage());// 实际发生的异常信息
      15         resultError.setExceptionName(ex.getClass().getName());// 实际异常的名字
      16         resultError.setPath(request.getRequestURI());// 异常RUI
      17         return resultError;
      18     }
      19     
      20 }
    3. 编写控制器Controller定义后台错误
       1 @Controller
       2 public class HtmlController {
       3 
       4     @RequestMapping("/indexHtml")
       5     public String indexHtml(Model model) {
       6         model.addAttribute("url","XSGE个人网站:http://www.xsge123.com");
       7         System.out.println("测试"+(10/0));
       8         return "indexHtml";
       9     }
      10 }
    4. 使用Postman测试访问

       页面访问测试结果:正常响应,但状态码是500

       
    5. 异常处理优化
      在异常处理器方法中,判断异常类型,定义更加细节的异常响应内容。
       1 // 该注解是异常处理器注解,可以对指定异常类型处理,执行注解标注的方法(只要发生指定异常都会被拦截)
       2 @ExceptionHandler(Throwable.class)    
       3 // 该注解用于指定异常处理方法执行后响应页面的HTTP状态码,HttpStatus是Spring内置的一个状态码枚举类,内定了详细的状态码及描述,当前获取的是500
       4 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 响应500
       5 public Object exceptonResponse(Exception ex,HttpServletRequest request) {
       6     ExceptionResponseResult resultError = new ExceptionResponseResult();
       7     if (ex instanceof NullPointerException) {// 如果捕获的异常为控空指针异常
       8         // ****设置异常信息*****
       9     } else if (ex instanceof ArithmeticException) {
      10         // ****设置异常信息*****
      11     }// *****
      12     return resultError;
      13 }

    二.JSR-303数据校验

      在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。然后仅仅前端页面的校验就能保证安全了吗?小朋友还是年轻,基础的前端攻击技术网上很多,所以,仅仅页面数据校验是不够的。JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。

      JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 。 Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint(约束)。(注意:此实现与 Hibernate ORM 没有任何关系)。

      校验规则及使用方法:关注博主SSM整合之数据校验!!!(目前资料已备没时间写,敬请期待!)等不及的可以查阅Githup官网,查看说明文档

      Jackson常用注解:

       @JsonIgnore:用于POJO属性,作用:使用该注解的指定字段将被返回(前台数据将不会获取该字段的结果)。
       @JsonFormat( pattern="yy MM- dd hh:mm: ss" , loca Le=" zh" , timezone="GMT+8"):用于POJO属性,作用:使用该注解的指定字段将被格式化日期格式果)。
      @JsonInclude(Include .NON NULI):用于POJO属性,作用:使用该注解的指定字段值如果为null,则不返回。
       @JsonProperty:用于POJO属性,作用:如果不希望被前台获取映射的真实属性名,使用该注解的指定字段将返回别名显示。

  • 相关阅读:
    单链表反转
    【华为悦读汇】技术发烧友:M-LAG
    Linux系列—策略路由、ip rule、ip route
    对象存储,为什么那么火?
    关于存储技术的最强入门科普
    OpenStack入门科普,看这一篇就够啦!
    虚拟化技术的本质
    师屎胜于熊便:FCoE iSCSI FC组网PK
    懂了!VMware/KVM/Docker原来是这么回事儿
    集群文件系统是什么
  • 原文地址:https://www.cnblogs.com/xsge/p/13892467.html
Copyright © 2011-2022 走看看