zoukankan      html  css  js  c++  java
  • Spring boot 拾遗 —— 错误验证

    1 前言

    Spring 的验证框架为我们提供了强大的验证功能,我们不但要会使用它,更要知道它工作的原理,这一文将简要点出 验证的基础基础流程,包括

    • spring 如果确定入参需要参与验证
    • spring 如何决定是抛出各种验证错误,还是将错误信息传递给开发人员
    • spring 如何为表单验证与 JSON 请求体验证产生的不同类型的错误
    • spring 如何调用我们编写的自定义全局错误处理器

    最后改写默认的全局错误处理器以尽可能覆盖各种错误

    2 从表单验证方式说起

    spring boot 提供了我们多种验证方式, 通常我们遵循 spring mvc 提供的思想,在控制层写下如下代码:

     1     @PostMapping("register")
     2     public Object register(@Validated RegisterForm form, BindiningResult result, HttpServletResponse response) {
     3         if (result.hasErrors()) {
     4             Map<String, String> map = new HashMap<>(4);
     5             result.getFieldErrors().forEach(e -> map.put(e.getObjectName(), e.getDefaultMessage()));
     6             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
     7             return map;
     8         }
     9         return userService.register(form) ? "success" : "fail";
    10     }

    在实际的开发中如果采用这种方式,太多重复性劳动的方式会使得开发变得枯燥且不好维护。

    2.1 抛弃 BindiningResult

    让我们抛弃 BindingResult,改写方法成下方这一简单的形式,然后提交一个表单

    1     @PostMapping("register")
    2     public Object register(@Validated RegisterForm form) {
    3         return userService.register(form) ? "success" : "fail";
    4     }

    然后观察其抛出的错误:

    2.2 RestControllerAdvice 捕捉 BindException 错误

    上边的返回是不可读的,不可能直接交付给前端,下边是一个适用于 RestController 的全局错误处理器,没有什么特别的

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @RestControllerAdvice(annotations = RestController.class) 
    6
    public class ExceptionResolver { 7 8 @ExceptionHandler(BindException.class) 9 @ResponseStatus(HttpStatus.BAD_REQUEST) 10 public Map<String, String> bindException(@Nonnull BindException ex) { 11 Map<String, String> map = new HashMap<>(4); 12 ex.getFieldErrors().forEach(e -> map.put(e.getField(), e.getDefaultMessage())); 13 return map; 14 } 15 }

    2.3 BindException 错误的产生来源

    在 spring 调用 ModelAttributeMethodProcessor#resolveArgument 方法处理方法参数的时候,

    会调用 validateIfApplicable 尝试对每一个有 Validated 或者 Valid 注解的参数进行验证,

    如果验证的结果存在错误,就会检查是否调用的方法是否入参存在  Errors 的实现,即我们传进去的 BindingResult,如果有则将错误信息放进去  BindingResult , 否则抛出一个 BindException 错误。 

    2.4 RestControllerAdvice 的查找与调用

    spring 调用 ExceptionHandlerExceptionResolver#getExceptionHandlerMethod 方法查找我们配置的 ExceptionHandler 

    并且测试是否我们配置的 Handler 是否支持这个方法,在这里我们配置了支持注解 @RestController,需要注意的是,如果  @ExceptionHandler 注解上没有任何属性,将会直接匹配到

     

     2.4.1 ReponseStatus 的查找

    3 JSON 请求体验证

    与表单验证类似,但是验证是在 RequestResponseBodyMethodProcessor#resolveArgument 方法中进行的,抛出的类型也不同,是:MethodArgumentNotValidException。

    3.1 验证代码准备

        @PostMapping("register")
        public Object register(@RequestBody @Validated RegisterForm form) {
            return userService.register(form) ? "success" : "fail";
        }

    3.2 RestControllerAdvice 捕捉 BindException 错误

    与表单验证不同,我们这次要捕捉 MethodArgumentNotValidException 错误:

    1     @ExceptionHandler(MethodArgumentNotValidException.class)
    2     @ResponseStatus(HttpStatus.BAD_REQUEST)
    3     public Map<String, String> methodArgumentNotValidException(@Nonnull MethodArgumentNotValidException ex) {
    4         Map<String, String> map = new HashMap<>(4);
    5         ex.getBindingResult().getFieldErrors().forEach(e -> map.put(e.getField(), e.getDefaultMessage()));
    6         return map;
    7     }

    4 预设的全局错误处理

    作为一个普通的开发人员,总会有很多遗漏,此时,让我们的 全局错误处理器  继承 ResponseEntityExceptionHandler 是个不错的选择,相应的,我们只要重写对应的表单验证与 JSON 请求验证的相关逻辑:

     1 package cn.pancc.springboot.examples.security.global;
     2 
     3 import org.springframework.http.HttpHeaders;
     4 import org.springframework.http.HttpStatus;
     5 import org.springframework.http.ResponseEntity;
     6 import org.springframework.validation.BindException;
     7 import org.springframework.web.bind.MethodArgumentNotValidException;
     8 import org.springframework.web.bind.annotation.RestController;
     9 import org.springframework.web.bind.annotation.RestControllerAdvice;
    10 import org.springframework.web.context.request.WebRequest;
    11 import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
    12 
    13 import java.util.HashMap;
    14 import java.util.Map;
    15 
    16 /**
    17  * @author pancc
    18  * @version 1.0
    19  */
    20 @RestControllerAdvice(annotations = RestController.class)
    21 public class ErrorHandlerExceptionResolver extends ResponseEntityExceptionHandler {
    22 
    23     @Override
    24     protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    25         Map<String, String> errors = new HashMap<>(4);
    26         ex.getBindingResult().getFieldErrors().forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
    27         return new ResponseEntity<>(errors, status);
    28     }
    29 
    30     @Override
    31     protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    32         Map<String, String> errors = new HashMap<>(4);
    33         ex.getBindingResult().getFieldErrors().forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
    34         return new ResponseEntity<>(errors, status);
    35     }
    36 }
  • 相关阅读:
    多态与鸭子类型
    mixin与派生
    4.9作业
    property
    继承
    封装
    《梦断代码》阅读笔记1
    阅读笔记3——《大道至简》第四、五、六章
    阅读笔记2——《大道至简》第二、三章
    阅读笔记1——《大道至简》第一章
  • 原文地址:https://www.cnblogs.com/siweipancc/p/Spring_boot_pick_up_validation_of_errors.html
Copyright © 2011-2022 走看看