• JSR303校验+统一异常处理细节+同一字段多个校验注解的结果如何处理


    JSR303

    • 1)、导入 javax.validation、hibernate-validator依赖,尤其是第二个,在springboot应用中使用校验,必须导入
    • 2)、给Bean的字段添加校验注解:javax.validation.constraints,并定义自己的message提示
      • @NotNull: CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集(size = 0)。
      • @NotEmpty: CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0。
      • @NotBlank: String 不是 null 且 至少包含一个字符
    • 3)、开启校验功能 使用@Valid
      • 效果:校验错误以后会有默认的响应;
    • 4)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
    • 5)、分组校验(多场景的复杂校验)
      - @NotBlank(message = "品牌名必须提交",groups ={AddGroup.class,UpdateGroup.class})
      - @Validated({AddGroup.class}),给校验注解标注什么情况需要进行校验
      - 默认没有指定分组的字段校验使用注解@Valid,在分组校验情况下,只会在@Validated({AddGroup.class})生效;
    • 6)、自定义校验
      • 1、编写一个自定义的校验注解
      • 2、编写一个自定义的校验器 ConstraintValidator
      • 3、关联自定义的校验器和自定义的校验注解
        • @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
    • 统一的异常处理
      • @ControllerAdvice
      • 编写异常处理类,使用@ControllerAdvice。
      • 使用@ExceptionHandler标注方法可以处理的异常。

      举例

      要校验的实体类

      • 注意 username, password, code 字段都有多个校验注解
    	@Data
    	public class RegisterVO {
    	
    	    
    	    @NotBlank(message = "用户名不能为空")
    	    @Length(min = 4, max = 20, message = "用户名长度为4-20字符")
    	    private String username;
    	
    	    @NotBlank(message = "密码不能为空")
    	    @Length(min = 8, max = 16, message = "密码长度为8-16字符")
    	    private String password;
    	
    	    @Pattern(regexp = "^1[3-9][0-9]{9}$", message = "手机号格式不合法")
    	    private String phone;
    	
    	    @NotNull(message = "验证码不能为空")
    	    @Pattern(regexp = "^[0-9]{6}$", message = "验证码为6位数字")
    	    private String code;
    	}
    

    控制器

    • 注意我这里没标@RequestBody注解,这个等会再说
    	@PostMapping("/register")
        public String register(@Validated RegisterVO registerVO) {
            // 校验出错会被异常处理器处理
            return "success";
        }
    

    异常处理器

    • 使用@ControllerAdvice和@ExceptionHandler组合
    • @ExceptionHandler标注在方法上,指定这个方法处理的是哪个异常
    • @ControllerAdvice指名这个类既是一个控制器,也是一个异常处理类,也就是说,你下面的方法,
      • 如果返回值是String,那么它也会被视图解析器处理,返回视图页面;
      • 如果你想让它返回json数据,那么加上@ResponseBody注解即可;
      • 如果你这个类所有方法最终都不返回视图,只返回json,那么很简单,直接把@
        ControllerAdvice换成@RestControllerAdvice即可。

    现在我们使用下面这个异常处理来处理对前端传来的数据RegisterVO 进行校验的结果。当数据校验失败时,会抛出异常,会抛出哪个异常呢,我们先直接使用Exception.class来接收,使用它总是没错的。

    • 我们把所有校验结果封装成一个map,key是字段名字,value是校验出错的信息。
    @ControllerAdvice
    public class AuthExceptionHandler {
    
        /**
         * 注册,表单提交数据格式校验失败;返回json数据
         * @param e
         * @return
         */
        @ResponseBody
        @ExceptionHandler({Exception.class})
        public Map<String, String> exceptionHandler(Exception e) {
            BindingResult bindingResult = e.getBindingResult();
            // 使用stream api
            Map<String, String> map = bindingResult.getFieldErrors()
            	.stream()
            	.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
            // 上面那种是种简写,如果你不熟悉,这样也可以
            // Map<String, String> map = new HashMap<>();
            // bindingResult.getFieldErrors().forEach(fieldError -> {
            //     String field = fieldError.getField();
            //     String message = fieldError.getDefaultMessage();
            //     map.put(field, message);
            // });
            return map;
        }
    
    }
    

    验证

    然后我们让前端提交一个全空的数据
    在这里插入图片描述
    我们期待的返回给我们校验结果,以json数据返回。但是它报错了!!!

    java.lang.IllegalStateException: Duplicate key 用户名长度为4-20字符
    

    DuplicateKey一般是两个相同键出现,比如你在数据库插入两条id字段相同的记录,假设id是唯一索引,此时就会抛出DuplicateKeyXXXException,仔细查看报错内容,发现出错的代码位置

    at com.vivi.auth.exception.AuthExceptionHandler.bindingExceptionHandler(AuthExceptionHandler.java:38)
    

    也就是我们写的异常处理的这一行

    Map<String, String> map = bindingResult.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    

    所以可以得出结果,肯定是这个校验结果是,某个key出现了两次,导致无法封装成功,因此他也不知道同一个键,第二次的值是要丢掉还是替换第一个呢?

    所以这两个相同键是哪里来的?还记得我开始写的 RegisterVO 类么,有些字段上面有两个校验注解,那么是这个原因么,我们可以在异常处理方法上debug,在它封装成map之前,看一下它这个校验结果里面有什么

    我们发现有6个校验错误,其中 username 和 password 都出现了两次,正如我们的校验注解缩写,每个字段都有两个校验

        @NotBlank(message = "用户名不能为空")
        @Length(min = 4, max = 20, message = "用户名长度为4-20字符")
        private String username;
    
        @NotBlank(message = "密码不能为空")
        @Length(min = 8, max = 16, message = "密码长度为8-16字符")
        private String password;
    

    在这里插入图片描述
    我们在点开看一下,比如 username 的两个结果,是不是我们的校验注解所写的message,的确是的!
    在这里插入图片描述

    总结:
    • 某个字段上有两个或多个校验注解时,如果两个的规则都被触发,那么就会有两个键相同(都是这个字段名),值不同(两个校验各自的message)的校验结果。
    • 这时我们想把它封装成一个map,直接使用使用之前那种写法肯定是不行的,我们可以简单修改一下,既然是同一个字段的校验结果,将这两个信息联合起来就好了呀,比如入下面这样:
    	bindingResult.getFieldErrors().forEach(fieldError -> {
    	    String field = fieldError.getField();
    	    String message = fieldError.getDefaultMessage(); // 当次结果
    	    String msg = map.getOrDefault(field, ""); // 上次校验结果
    	    map.put(field, msg + "," + message); // 连接起来再赋值
    	});
    

    最后,还有一个问题,就是之前说的 数据校验失败抛出的异常到底是什么类型

    我这里简单说一下,有兴趣的朋友可以自己翻翻源码。

    • 如果前端是form表单提交数据,数据格式就为 'application/x-www-form-urlencoded;charset=UTF-8' ,Spring 使用 FormHttpMessageConverter 转化请求体(表单数据),到封装成对象 ,校验失败抛出异常 BindException;这种情况下,我们在controller接收时也不能使用@RequestBody,否则会报错 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported, org.springframework.web.HttpMediaTypeNotSupportedException
    • 如果前端是ajax或别的方式,以json格式传输数据,那我们接收时就需要添加 @RequestBody ,Spring按照json格式进行解析以及封装,校验失败抛出 MethodArgumentNotValidException
    • 最后,如果你还是不清楚,你就使用Exception来处理,肯定能成功,你再打印一下异常的类型就能看到它具体是哪个类了!
  • 相关阅读:
    run C source file like a script
    shared_ptr注意点
    C++ #if #endif #define #ifdef #ifndef #if defined #if !defined详解 (转)
    linux切换g++
    std::forward_list
    有关typename
    win7下 mysql安装(mysql-5.7.18-winx64.zip)
    c++ 库函数返回的字符串指针是否需要手动释放
    c++ const char *[] or char [][]
    校园资源助手
  • 原文地址:https://www.cnblogs.com/codervivi/p/14285425.html
Copyright © 2020-2023  润新知