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 }

 

posted on 2020-05-31 01:55  四维胖次  阅读(396)  评论(0编辑  收藏  举报