最近在重构老项目的代码,发现校验入参占用了很多代码,之前我对这一块的认识局限于使用StringUtils等工具来多个if块进行判断,代码是没什么问题,但是总写这些令人生烦,毕竟写代码也要讲究优雅的嘛,于是呢我就研究了一下JavaEE Api 上的校验类,基本上推翻了我之前对校验注解之类的认识,在这里记录一下所得。

  1. @NotNull @NotBlank @NotEmpty 这三个注解的区别以及使用

    @NotNull:校验入参不能为空,无法正确校检长度为0的字符串或以完全为空格的字符串

    @NotBlank: 包含@NotNull的功能,可以校验字符串内容是否为空

    @NotEmpty: 校验传入集合是否为空

    当以上几个注解校验不被满足的时候,就会抛出异常,打印出默认的消息内容,

    age not be null这样的错误信息

    想自定义错误信息可以如:@NotNull(message = "信息不能为空")来定义

  2. @Valid的使用

    如果入参为一个对象,想校验这个对象内容是否正确的时候,会用到1节中所讲的几个注解,

    但是此时会有一个问题:如果不使用@Valid注解,1节中的注解在入参过程中是不生效的,只有保存的时候才会被校验(不排除PO和DTO是同一个对象的情况)

    当然,这有一个大前提,就是没有其它对入参校验的方法执行。

    **所以如果是简单为了让异常报出来而不是直接返回的时候可以只用@Valid

  3. 自定义校验与错误信息的处理

    (1)使用BindingResult对象接收被@Valid校验不通过的错误信息

        /**
    * 使用BindingResult与@Valid配合的方式
    * 拿到错误信息,带部分堆栈
    */
    @PostMapping("/validationTest6")
    public String validationTest6(@RequestBody @Valid User user,
    BindingResult result){
    if(result.hasErrors()){
    //取一条错误信息
    ObjectError next = result.getAllErrors().iterator().next();
    log.error("error={}", next);
    //后边可以自己返回错误信息也可以自定义
    return next.toString();
    }
    //do somethings
    return "校验通过";
    } /**
    * 使用BindingResult与@Valid配合的方式
    * 只拿到错误信息
    */
    @PostMapping("/validationTest7")
    public String validationTest7(@RequestBody @Valid User user,
    BindingResult result){
    if(result.hasErrors()){
    //取一条错误信息
    ObjectError next = result.getAllErrors().iterator().next();
    String defaultMessage = next.getDefaultMessage();
    log.error("error={}", defaultMessage);
    //后边可以自己返回错误信息也可以自定义
    return defaultMessage;
    }
    //do somethings
    return "校验通过";
    }

    (2)使用Validator对象,不信赖于@Valid的实现

        /**
    * 使用Validator未抽取工具类时的实现
    */
    @PostMapping("/validationTest9")
    public String validationTest9(@RequestBody User user){
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();
    Set<ConstraintViolation<User>> validate = validator.validate(user);
    if(!validate.isEmpty()){
    ConstraintViolation<User> next = validate.iterator().next();
    String message = next.getMessage();
    log.error("error={}",message);
    return message;
    }
    //do somethings
    return "校验通过";
    }

    上边给大家介绍了两种方法,其中第二种看起来代码更多一些,其实这应该就是没有使用@Valid的问题了,因为你并不知道@Valid注解校验错误代码有多少。效率基本差不多少

    下边为大家提供一个工具类,分别提供了上边两种方式的封装,如果只想使用BindingResult方式,那么大可以去除用不到的代码

  4. 封装的工具类与使用

    (1)工具类代码

    package com.cnblogs.hellxz.myutils;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.ObjectError; import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    import java.util.Set; /**
    * <b>类名</b>: ValidatorUtils
    * <p><b>描 述</b> 校验入参 </p>
    *
    * <p><b>创建日期</b>: 8/24/18 12:54 PM </p>
    *
    * @author HELLXZ 张
    * @version 1.0
    * @since jdk 1.8
    */
    public class ValidatorUtils { private static final Logger log = LoggerFactory.getLogger(ValidatorUtils.class);
    private static final Validator validator; static {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
    } /**
    * 分组进行校验或完整校验
    * 区别在于是否传groups类
    * ps:分组的好处在于同一个DTO分别给不同的方法指定不同的校验参数时候非常有用
    * 也就不用再去重写一个新的相同字段的DTO指定不同校验字段的操作,更灵活
    * @param object 被校验的DTO对象
    * @param groups 分组类,可以是接口也可以是类,仅作为标识
    * @return 想要返回的结果
    */
    public static <T> String validEntity(final T object, Class... groups) {
    Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
    if (!violations.isEmpty()) {
    //这里只取第一条错误,防止返回参数过多
    ConstraintViolation<T> violation = violations.iterator().next();
    log.error(violation.getMessage());
    //下面的代码可以使用公司或个人习惯的返回工具类也可以
    return "{\"code\":\"400\",\"msg\":\"" + violation.getMessage() + "\"}";
    }
    return null;
    } /**
    * 校验入参对象的某个属性是否满足要求,跳过其它校验, 支持分组
    * @param object 被校验的DTO对象
    * @param propertyName DTO对象内的属性名
    * @param groups 分组名
    * @return 校验出错返回的结果
    */
    public static <T> String validProperty(final T object, String propertyName,
    Class... groups) {
    Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName, groups);
    if (!violations.isEmpty()) {
    //这里只取第一条错误,防止返回参数过多
    ConstraintViolation<T> violation = violations.iterator().next();
    log.error(violation.getMessage());
    //下面的代码可以使用公司或个人习惯的返回工具类也可以
    return "{\"code\":\"400\",\"msg\":\"" +
    violation.getMessage() + "\"}";
    }
    return null;
    } /**
    * 使用BindingResult与@Valid注解一起使用实现的工具类
    * @param result BindingResult对象
    * @return 结果串
    */
    public static String validEntity(BindingResult result){
    if(result.hasErrors()){
    //取一条错误信息
    ObjectError next = result.getAllErrors().iterator().next();
    String defaultMessage = next.getDefaultMessage();
    log.error("error={}", defaultMessage);
    //后边可以自己返回错误信息也可以自定义
    return "{\"code\":\"400\",\"msg\":\"" + defaultMessage + "\"}";
    }
    return null;
    }
    }

    (2) 工具类的使用

        private static Logger log =
    LoggerFactory.getLogger(ValidateController.class); /**
    * 检查User对象注解在A组内的校验
    */
    @PostMapping("/validationTest1")
    public String validationTest1(@RequestBody User user){
    String valid = ValidatorUtils.validEntity(user, A.class);
    if(null != valid) return valid;
    //do somethings
    return "校验通过";
    } /**
    * 检查User对象注解在B组内的校验
    */
    @PostMapping("/validationTest2")
    public String validationTest2(@RequestBody User user){
    String valid = ValidatorUtils.validEntity(user, B.class);
    if(null != valid) return valid;
    //do somethings
    return "校验通过";
    } /**
    * 检查User对象注解在A组和B组内的校验
    * ps: 是A组内校验加上B组内校验,不是校验同时在两个组!
    */
    @PostMapping("/validationTest3")
    public String validationTest3(@RequestBody User user){
    String valid = ValidatorUtils.validEntity(user, A.class, B.class);
    if(null != valid) return valid;
    //do somethings
    return "校验通过";
    } /**
    * 校验入参对象中指定字段
    * 其中分组可以不传,如果传的话,请注意该字段必须在该组下,否则不会被校验
    */
    @PostMapping("/validationTest4")
    public String validationTest4(@RequestBody User user){
    String valid = ValidatorUtils.validProperty(user,"age", B.class);
    if(null != valid) return valid;
    //do somethings
    return "校验通过";
    } /**
    * 验证上边Test4的说法
    */
    @PostMapping("/validationTest5")
    public String validationTest5(@RequestBody User user){
    String valid = ValidatorUtils.validProperty(user,"age", A.class);
    if(null != valid) return valid;
    //do somethings
    return "校验通过";
    } /**
    * 使用方法7抽的工具类
    */
    @PostMapping("/validationTest8")
    public String validationTest8(@RequestBody @Valid User user,
    BindingResult result){
    String errorMsg = ValidatorUtils.validEntity(result);
    if(StringUtils.isNotBlank(errorMsg)) return errorMsg;
    //do somethings
    return "校验通过";
    }

    (3) 补充:User类

    public class User {
    
        @NotBlank(message = "用户名不能为空串",groups = A.class)
    private String username;
    @NotNull(message = "年龄不能为空",groups = B.class)
    private String age;
    @NotBlank(message = "身高不能为空", groups = {A.class, B.class})
    private String height;
    @NotEmpty(message = "孩子列表不能为空")
    private List<Object> childs; @Email(message = "email不正确")
    private String email; //省略get set 方法
    }
  5. 扩展部分

    空检查

    @Null:验证某参数必须为空 ps: 没有过 =. = ,其余的空检查在上边已经讲了

    长度检查

    @Size(min=, max=): 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内

    @Length(min=, max=): 长度是否在范围内

    Booelan检查

    @AssertTrue: 验证 Boolean 对象是否为 true

    @AssertFalse: 验证 Boolean 对象是否为 false

    数值检查:建议用在基础类型包装类型上和String类型上

    @Min: 验证最小值

    @Max: 验证最大值

    @DecimalMax:小数数值不能大于设置的值,小数存在精度

    @DecimalMin:小数数值不能小于设置的值,小数存在精度

    @Digits: 验证数字和字符串组成是否合法

    范围检查

    @Range(min=, max=):验证对象最小与最大的范围

    号码检查

    @CreditCardNumber: 信用卡验证(hibernate的实现)

    @Email: 验证是否是邮件地址,如果为null,则不进行验证,通过验证(validation和hibernate均有实现)

    日期检查

    @Past:验证 Date 和 Calendar 对象是否在当前时间之前,null 被认为是通过验证

    @Future:验证 Date 和 Calendar 对象是否在当前时间之后 ,null 被认为是通过验证

    正则表达式检查

    @Pattern(regexp = ""): 使用正则表达式验证String,接受字符序列,regexp必填,所修饰为null时认为是通过验证

  6. 终了

    憋了一晚上才想通校验的实际用法,参考了一些的博文,把最大部分的内容引用粘在下面了,基本是官方文档和一个外文网站

    https://docs.oracle.com/javaee/7/api/toc.htm

    https://docs.jboss.org/hibernate/stable/validator/api/

    https://www.baeldung.com/javax-validation

    https://blog.csdn.net/yanfeng918/article/details/42618593

    本文代码:https://github.com/HellxZ/MyUtils.git

    声明:本文内容未经许可可以转载,但请注明出处

SpringMvc数据校验@Valid等注解的使用与工具类抽取的更多相关文章

  1. SpringMVC数据校验并通过国际化显示错误信息

    目录 SpringMVC数据校验并通过国际化显示错误信息 SpringMVC数据校验 在页面中显示错误信息 通过国际化显示错误信息 SpringMVC数据校验并通过国际化显示错误信息 SpringMV ...

  2. 《Java从入门到放弃》入门篇:springMVC数据校验

    昨天我们扯完了数据传递,今天我们来聊聊数据校验的问题.来,跟着我一起读:计一噢叫,一按艳. 在springMVC中校验数据也非常简单,spring3.0拥有自己独立的数据校验框架,同时支持JSR303 ...

  3. springMVC数据校验与单文件上传

    spring表单标签:    <fr:from/> 渲染表单元素    <fr:input/>输入框组件    <fr:password/>密码框组件标签    & ...

  4. SpringMVC——数据校验

    数据校验在web应用里是非常重要的功能,尤其是在表单输入中.在这里采用Hibernate-Validator进行校验,该方法实现了JSR-303验证框架支持注解风格的验证. 一.导入jar包 若要实现 ...

  5. Spring MVC 数据校验@Valid

    先看看几个关键词 @Valid @Pattern @NotNull @NotBlank @Size BindingResult 这些就是Spring MVC的数据校验的几个注解. 那怎么用呢?往下看 ...

  6. 《SpringMVC从入门到放肆》十三、SpringMVC数据校验

    上一章,我们学习了SpringMVC的自定义类型转换器,但是如果转换后的数据传递到Controller的方法中,忽然发现有某些属性为Null了,这怎么办?我们需要一种有效的数据校验机制,来对数据进行有 ...

  7. 从深处去掌握数据校验@Valid的作用(级联校验)

    每篇一句 NBA里有两大笑话:一是科比没天赋,二是詹姆斯没技术 相关阅读 [小家Java]深入了解数据校验:Java Bean Validation 2.0(JSR303.JSR349.JSR380) ...

  8. SpringMVC 数据校验(JSR-303)

    项目中,通常使用较多的是前端的校验,比如页面中js校验以及form表单使用bootstrap校验.然而对于安全要求较高点建议在服务端进行校验. 服务端校验: 控制层controller:校验页面请求的 ...

  9. Springmvc数据校验

    步骤一:导入四个jar包 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=" ...

随机推荐

  1. Delphi中 弹出框的用法

    Delphi中的提示框有 Application.MessageBox  ShowMessage messagedlg 个人认为 相对来说 Application.MessageBox 更加灵活 也相 ...

  2. Linux内核分析 计算机是如何工作的——by王玥

    1.冯诺依曼体系结构:也就是指存储程序计算机 硬件(存储程序计算机工作模式): 软件(程序员角度): 2.API:程序员与计算机的接口界面 ABI:程序与CPU的接口界面 3.X86的实现: 4.X8 ...

  3. spring整合redis(jedis)

    真是一步一个坑阿,学点新技术,这么难,这个异常: java.lang.IllegalStateException: Could not load TestContextBootstrapper [nu ...

  4. Linux命令(十一) 显示文件类型 file

    命令介绍 file 命令是用来显示文件的类型,对于每个给定的参数,该命令试图将文件分类,分辨的类型有文本文件.可执行文件.压缩文件.或其它可理解的数据格式. 常用参数介绍 -b 不显示文件名称,只显示 ...

  5. WebApplication与WebSite区别

    1. WebApplication(Web应用程序)和WebSite(网站)的区别:WebSite是为了兼容从ASP转过来的开发人员的习惯而存在的,用起来简单,例如:不需要创建命名控件.C#代码修改以 ...

  6. 2017-2018 ACM-ICPC, Asia Daejeon Regional Contest C(记忆化搜索)

    C题 Problem C Game Map 思路: 之前暴力搜索写了好几发,一直超时,后面看其他人的题解发现要用记忆化搜索..直接暴力搜的话有太多重 复的计算. dist u 表示以u 为起点所能走的 ...

  7. IDEA导出jar包后运行报错 找不到或无法加载主类

    开发工具:IDEA16 运行环境:ubuntu 问题:根据网上的Idea导出jar包的方法,将我的项目导出jar包后运行报错:找不到或无法加载主类.   为了找到这个原因,我重新搭建了一个测试例子,在 ...

  8. 字符编码问题mysql

    2019-02-27 07:32:17.108 ERROR 21745 --- [nio-8086-exec-2] c.h.h.rest.configurer.WebMvcConfigurer : 接 ...

  9. luogu2678 [NOIp2015]跳石头 (二分答案+贪心)

    先二分出一个x,我们要算使最近的跳跃距离>=x的最少移除数量是否<=M就可以了 然后就别dp了...贪心就完事了...我肯定能不移就不移比较好... #include<bits/st ...

  10. (转)Maven学习总结(八)——使用Maven构建多模块项目

    孤傲苍狼只为成功找方法,不为失败找借口! Maven学习总结(八)——使用Maven构建多模块项目 在平时的Javaweb项目开发中为了便于后期的维护,我们一般会进行分层开发,最常见的就是分为doma ...