SpringBoot入门到精通(七):统一异常处理与整合JSR303校验(2021最新最易懂)

统一异常处理与整合JSR303校验(2021最新最易懂)

一.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属性,作用:如果不希望被前台获取映射的真实属性名,使用该注解的指定字段将返回别名显示。

posted @ 2020-11-12 19:45  净重21克  阅读(1465)  评论(0编辑  收藏  举报