概述

使用场景

通常在遇到大量的参数进行校验时,业务中还要抛出异常或者不断的返回异常的校验信息;在代码中相对冗长,充满了if-else这种校验代码,相当痛苦。

为什么选择validator

javax.validation的一系列注解可以帮我们完成参数校验,免去繁琐的串行校验。

如果在代码中自己处理,就会又臭又长。

    /*** 走串行校验** @param userVO* @return*/@PostMapping("/save/serial")public Object save(@RequestBody UserVO userVO) {String mobile = userVO.getMobile();//手动逐个 参数校验~ 写法if (StringUtils.isBlank(mobile)) {return RspDTO.paramFail("mobile:手机号码不能为空");} else if (!Pattern.matches("^[1][3,4,5,6,7,8,9][0-9]{9}$", mobile)) {return RspDTO.paramFail("mobile:手机号码格式不对");}//抛出自定义异常等~写法if (StringUtils.isBlank(userVO.getUsername())) {throw new BizException(Constant.PARAM_FAIL_CODE, "用户名不能为空");}// 比如写一个map返回if (StringUtils.isBlank(userVO.getSex())) {Map<String, Object> result = new HashMap<>(5);result.put("code", Constant.PARAM_FAIL_CODE);result.put("msg", "性别不能为空");return result;}//.........各种写法 ...userService.save(userVO);return RspDTO.success();}

什么是javax.validation

JSR303是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面(面向注解编程的时代),就可以在需要校验的时候进行校验了,在SpringBoot中已经包含在starter-web中,再其他项目中可以引用依赖,并自行调整版本。

     <!--jsr 303--><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>1.1.0.Final</version></dependency><!-- hibernate validator--><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.2.0.Final</version></dependency>

注解说明

校验注解 校验的数据类型 说明
@NotNull 任意类型 校验注解的元素值不为null
@Null 任意类型 校验注解的元素值是null
@NotBlank CharSequence子类型 验证注解的元素值不为空(不为null、去除首位空格后长度不为0),不同与@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格。
@NotEmpty CharSequence子类型、Collection、Map、数组 验证注解的元素值不为null且不为空(字符串长度不为0,集合大小不为0)
@Valid 任何非原子类型,级联验证。比如验证User对象的属性Address对象 指定递归验证关联的对象如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象,在地址对象上加@Valid注解即可级联验证。
@AssertFalse Boolean,boolean 验证注解的元素值是false
@AssertTrue Boolean,boolean 验证注解的元素值是true
@Min(value=xxx) BigDecimal,BigInteger,byte,short,int,long,等任何Number或CharSequence(存储的是数字)子类型,, 验证注解的元素值大于等于@Min指定的value值
@Max(value=xxx) 和@Min要求一样 验证注解的元素值小于等于@Max指定的value值
@DecimalMax(value=xxx) 和@Min要求一样 验证注解的元素值大于等于@DecimalMin指定的value值
@DecimalMin(value=xxx) 和@Min要求一样 验证注解的元素值小于等于@DecimalMax指定的value值
@Digits(integer=整数位数,fraction=小数位数) 和@Min要求一样 验证注解的元素值的整数位数和小数位数的上限
@Size(min=下限,max=上限) 字符串、Collection、Map、数组等 验证注解的元素值在min和max(包含)指定区间之内,如字符长度、集合大小
@Past java.util.Date,java.util.Calendar,Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间早
@Future 与@Past要求一样 验证注解的元素值(日期类型)比当前时间晚
@Length(min=下限,max=上限) CharSequence子类型 验证注解的元素值长度在min和max区间内
@Range(min=最下值,max=最大值) BigDecimal,BigInteger,CharSequence,byte,short,int,long等原子类型和包装类型 验证注解的元素值在最小值和最大值之间
@Email(regexp=正则表达式,flag=标志的模式) CharSequence子类型(如String) 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式
@Pattern(regexp=正则表达式,flag=标志的模式) String,任何CharSequence的子类型 验证注解的元素值与指定的正则表达式匹配

此处只列出Hibernate Validator提供的大部分验证约束注解,请参考hibernate validator官方文档了解其他验证约束注解和进行自定义的验证约束注解定义。

Spring数据校验注解

校验注解 使用范围 说明
@Validated 任何非原子类 @Valid:没有分组的功能; @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上; @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制; @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上; 两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

使用示例

@Validated声明要检查的参数

 /*** 走参数校验注解** @param userDTO* @return*/@PostMapping("/save/valid")public RspDTO save(@RequestBody @Validated UserDTO userDTO) {userService.save(userDTO);return RspDTO.success();}

对参数的字段进行注解标注

import lombok.Data;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;/*** @ClassName: UserDTO* @Description: 用户传输对象*/
@Data
public class UserDTO implements Serializable {private static final long serialVersionUID = 1L;/*** 用户ID*/@NotNull(message = "用户id不能为空")private Long userId;/** 用户名*/@NotBlank(message = "用户名不能为空")@Length(max = 20, message = "用户名不能超过20个字符")@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")private String username;/** 手机号*/@NotBlank(message = "手机号不能为空")@Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")private String mobile;/**性别*/private String sex;/** 邮箱*/@NotBlank(message = "联系邮箱不能为空")@Email(message = "邮箱格式不对")private String email;/** 密码*/private String password;/*** 创建时间 */@Future(message = "时间必须是将来时间")private Date createTime;}

在全局校验中增加校验异常

MethodArgumentNotValidException是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理,其他异常需要处理ConstraintViolationException异常进行处理。

  • 为了优雅一点,我们通常将参数异常,业务异常,统一做成一个全局异常,将控制层的异常包装到我们自定义的异常中。

import com.boot.lea.mybot.dto.RspDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;/*** @ClassName: GlobalExceptionHandler* @Description: 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {private Logger logger = LoggerFactory.getLogger(getClass());private static int DUPLICATE_KEY_CODE = 1001;private static int PARAM_FAIL_CODE = 1002;private static int VALIDATION_CODE = 1003;/*** 处理自定义异常*/@ExceptionHandler(BizException.class)public RspDTO handleRRException(BizException e) {logger.error(e.getMessage(), e);return new RspDTO(e.getCode(), e.getMessage());}/*** 方法参数校验*/@ExceptionHandler(MethodArgumentNotValidException.class)public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {logger.error(e.getMessage(), e);return new RspDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage());}/*** ValidationException*/@ExceptionHandler(ValidationException.class)public RspDTO handleValidationException(ValidationException e) {logger.error(e.getMessage(), e);return new RspDTO(VALIDATION_CODE, e.getCause().getMessage());}/*** ConstraintViolationException*/@ExceptionHandler(ConstraintViolationException.class)public RspDTO handleConstraintViolationException(ConstraintViolationException e) {logger.error(e.getMessage(), e);return new RspDTO(PARAM_FAIL_CODE, e.getMessage());}@ExceptionHandler(NoHandlerFoundException.class)public RspDTO handlerNoFoundException(Exception e) {logger.error(e.getMessage(), e);return new RspDTO(404, "路径不存在,请检查路径是否正确");}@ExceptionHandler(DuplicateKeyException.class)public RspDTO handleDuplicateKeyException(DuplicateKeyException e) {logger.error(e.getMessage(), e);return new RspDTO(DUPLICATE_KEY_CODE, "数据重复,请检查后提交");}@ExceptionHandler(Exception.class)public RspDTO handleException(Exception e) {logger.error(e.getMessage(), e);return new RspDTO(500, "系统繁忙,请稍后再试");}
}

自定义参数注解

示例

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdentityCardNumberValidator.class)
public @interface IdentityCardNumber {String message() default "身份证号码不合法";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

这个注解是作用在Field字段上,运行时生效,触发的是IdentityCardNumberValidator这个验证类。

  • message定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制。
  • groups这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作。
  • payload主要是针对bean的,使用不多。

自定义Validator

这个是真正进行验证的逻辑代码

public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> {@Overridepublic void initialize(IdentityCardNumber identityCardNumber) {}@Overridepublic boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {return IdCardValidatorUtils.isValidate18Idcard(o.toString());}
}

使用自定义的注解

    @NotBlank(message = "身份证号不能为空")@IdentityCardNumber(message = "身份证信息有误,请核对后提交")private String clientCardNo;

使用groups的校验

同一个对象要复用,比如UserDTO在更新时候要校验userId,在保存的时候不需要校验userId,两种情况下都需要校验username,那就用上groups了:

先定义groups的分组接口Create和Update。

import javax.validation.groups.Default;public interface Create extends Default {}import javax.validation.groups.Default;public interface Update extends Default{}

再在需要校验的地方使用@Validated声明校验组

 /*** 走参数校验注解的 groups 组合校验** @param userDTO* @return*/@PostMapping("/update/groups")public RspDTO update(@RequestBody @Validated(Update.class) UserDTO userDTO) {userService.updateById(userDTO);return RspDTO.success();}

在DTO中的字段上定义好groups={}的分组类型

@Data
public class UserDTO implements Serializable {private static final long serialVersionUID = 1L;/*** 用户ID*/@NotNull(message = "用户id不能为空", groups = Update.class)private Long userId;/*** 用户名*/@NotBlank(message = "用户名不能为空")@Length(max = 20, message = "用户名不能超过20个字符", groups = {Create.class, Update.class})@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")private String username;/*** 手机号*/@NotBlank(message = "手机号不能为空")@Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误", groups = {Create.class, Update.class})private String mobile;/*** 性别*/private String sex;/*** 邮箱*/@NotBlank(message = "联系邮箱不能为空")@Email(message = "邮箱格式不对")private String email;/*** 密码*/private String password;/*** 创建时间 */@Future(message = "时间必须是将来时间", groups = {Create.class})private Date createTime;}

注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default否则,在你声明@Validated(Update.class)的时候,就会出现你在默认没添加groups = {} 的时候校验组@Email(message = “邮箱格式不对”)会不去校验,因为默认的校验组是groups = {Default.class}。

restful风格用法

在多个参数校验或者@RequestParam形式的时候,需要在controller上加注解@Validated

 @GetMapping("/get")public RspDTO getUser(@RequestParam("userId") @NotNull(message = "用户id不能为空") Long userId) {User user = userService.selectById(userId);if (user == null) {return new RspDTO<User>().nonAbsent("用户不存在");}return new RspDTO<User>().success(user);}@RestController
@RequestMapping("user/")
@Validated
public class UserController extends AbstractController {......
}

总结

统一参数校验,是为了减少我们代码大量的try catch的法宝。

参考

1. 优雅的校验参数-javax.validation

javax.validation相关推荐

  1. java注解返回不同消息,Spring MVC Controller中的一个读入和返回都是JSON的方法如何获取javax.validation注解的异常信息...

    Spring MVC Controller中的一个读入和返回都是JSON的方法怎么获取javax.validation注解的错误信息? 本帖最后由 LonelyCoder2012 于 2014-03- ...

  2. javax.validation.ParameterNameProvider

    今天在做spring和hibernate整合的时候遇到这个问题: Caused by: java.lang.NoClassDefFoundError: javax/validation/Paramet ...

  3. javax.validation.ValidationException: Unable to find a default provider

    2019独角兽企业重金招聘Python工程师标准>>> [ERROR] [2016-11-16 13:58:21 602] [main] (FrameworkServlet.java ...

  4. javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint

    使用hibernate validator出现上面的错误, 需要 注意 @NotNull 和 @NotEmpty  和@NotBlank 区别 @NotEmpty 用在集合类上面 @NotBlank ...

  5. ClassNotFoundException: javax.validation.ValidatorFactory

    ClassNotFoundException: javax.validation.ValidatorFactory spring mvc 程序. 浏览器一访问controller 则出现: Class ...

  6. [javax.validation]验证

    为什么80%的码农都做不了架构师?>>>    package main;import java.util.Set;import javax.validation.Constrain ...

  7. javax.validation.constraints.NotNull找不到

    javax.validation.constraints.NotNull找不到 javax.validation.constraints.NotNull 找不到 使用范例 附录 javax.valid ...

  8. javax.validation 校验 validator

    使用场景 通常在遇到大量的参数进行校验时使用: 什么是javax.validation JSR303是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们Ja ...

  9. 关于javax.validation.Validator校验的使用

    关于javax.validation.Validator校验的使用 对于要校验的实体类:其需要校验的字段上需要添加注解 实际例子 使用:首先要拿到 validator的子类 Validator val ...

最新文章

  1. 这24个高频存储问题,你一定要知道!如何不停机,安全更换数据库?大厂都怎么做MySQL到Redis同步的?...
  2. php 存储数据的方法,在PHP中存储可轻松编辑的配置数据的最快方法?
  3. 【IntelliJ IDEA】从资源文件读取出来就中文乱码的解决方法
  4. 集成ShareSDK里报错NSConcreteMutableData wbsdk_base64EncodedString]
  5. Android开发之带你轻松集成友盟统计
  6. 服务器php 不能运行框架,经验总结 PHP框架常见错误
  7. 无服务器架构 - 从使用场景分析其6大特性
  8. 极致业务基础开发平台
  9. 中考计算机考试试题山西注意事项,2021年山西省中考考试注意事项(3)
  10. 1 linux网络诊断命令工具
  11. python模块-logging的智商上限
  12. (转) EF三种编程方式的区别Database first ,Model first ,code first
  13. Adobe软件注册机
  14. 2014网络红人照片网络红人排行榜2014经典语录网络红人斌少
  15. httpwatch使用,浏览器内HTTP嗅探器
  16. CENTOS上的网络安全工具(二)ARKIME部署安装
  17. win10照片查看器_win10系统,图片查看器不见了咋办?教你调出传统图片查看器。...
  18. 音频处理-1 基础知识
  19. 炒币碰到熊市的应对方法,炒币者的八大口诀
  20. 先不听BAT高谈阔论,只看企业实际应用:数据中心架构如何演进?

热门文章

  1. ArcGIS教程:解决裁剪功能输出的数据集为空的情况。
  2. 计算机 打印 速度慢,处理打印机在打印文件时打印速度过慢的原因
  3. 吴恩达机器学习作业8(下)--- 推荐系统
  4. 3、★☛基于STM32的手机通过wifi控LED灯√♠★
  5. Linux隔离网络,linux – 隔离网络上的单个NTP服务器
  6. 微信直播小程序端集成源代码
  7. 桥接模式和装饰者模式的区别及理解
  8. [机器学习] SSE,MSE,RMSE,R-square指标讲解
  9. golang控制结构之select
  10. 热敏电阻和压敏电阻的区别与特性