视频剪辑软件 intellij idea下载 以太坊 merge binding mui jquery循环遍历 jq解析json div字体加粗 idea开发python mysql删除表 mysql更新 python的安装路径 java语言学习 java使用mysql java编程课程 java初学者 java怎么写接口 java定义变量 php连接mssql win7loader ps怎么插入表格 java游戏编程 计价软件 视频加字幕软件 lol语音包 pr蒙版 图片转pdf免费软件 cf小号 edquota Mapper unlocker下载 opencv是什么 ps怎么磨皮祛痘 视频抠图 易语言tv js绑定事件的方法 字体模糊 方正美黑简体 ps减去顶层
当前位置: 首页 > 学习教程  > 编程语言

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

2021/1/13 20:35:49 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

JSR303 1)、导入 javax.validation、hibernate-validator依赖,尤其是第二个,在springboot应用中使用校验,必须导入2)、给Bean的字段添加校验注解:javax.validation.constraints,并定义自己的message提示 NotNull: CharSequence, …

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来处理,肯定能成功,你再打印一下异常的类型就能看到它具体是哪个类了!

本文链接: http://www.dtmao.cc/news_show_600374.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?