JSR303数据校验
JSR 303 是Java为Bean数据合法校验提供的标准框架,已经包含在Java EE 6.0中。JSR是一个规范,它的核心接口时Validator,该接口根据目标对象类中所标注的校验注解进行数据校验,并得到校验结果。JSR303通过在Bean属性中标注类似@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
JSR 303 包含注解
注解名称
描述
@Null
被注解的元素必须为null
@NotNull
被注解的元素必须不为null
@AssertTrue
被注解的元素必须为true
@AssertFalse
被注解的元素必须为false
@Min(value)
被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)
被注解的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)
被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)
被注解的元素必须是一个数字,其值必须小于等于指定的最大值
@Size
被注解的元素的大小必须在指定的范围内
@Digits(integer,fraction)
被注解的元素必须是一个数字,其值必须在可接受的范围内
@Past
被注解的元素必须是一个过去的时间
@Future
被注解的元素必须是一个将来的时间
@Pattern(regex=,flag=)
被注解的元素必须符合指定的正则表达式
Hibernate Vakudator
Hibernate Vakudator是JSR303的一个参考实现,出来支持所有的标准注解外,还支持一些扩展注解
注解名称
描述
@NotBlank(message=)
验证字符串非null,并且长度必须大于0
@Email
被注解的元素必须是电子邮箱地址
@Length(min=,max=)
被注解的字符串的大小必须在指定的范围内
@NotEmpty
被注解的字符串必须非空
@Range(min=,max=,message=)
被注解的元素必须在合适的范围内
@URL
被注解的元素必须是合法的URL
ValidationMessages.properties 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 javax.validation.constraints.AssertFalse.message = 必须为 false javax.validation.constraints.AssertTrue.message = 必须为 true javax.validation.constraints.DecimalMax.message = 必须小于 ${inclusive == true ? 'or equal to ' : ''}{value} javax.validation.constraints.DecimalMin.message = 必须大于 ${inclusive == true ? 'or equal to ' : ''}{value} javax.validation.constraints.Digits.message = 数字值超出了边界(期望 <{integer} digits>.<{fraction} digits>) javax.validation.constraints.Email.message = 必须为格式规范的电子邮件地址 javax.validation.constraints.Future.message = 必须是未来的日期 javax.validation.constraints.FutureOrPresent.message = 必须是现在或将来的日期 javax.validation.constraints.Max.message = 必须小于或等于 {value} javax.validation.constraints.Min.message = 必须大于或等于 {value} javax.validation.constraints.Negative.message = 必须小于 0 javax.validation.constraints.NegativeOrZero.message = 必须小于或等于 0 javax.validation.constraints.NotBlank.message = 不得为空白 javax.validation.constraints.NotEmpty.message = 不得为空 javax.validation.constraints.NotNull.message = 不得为 null javax.validation.constraints.Null.message = 必须为 null javax.validation.constraints.Past.message = 必须是过去的日期 javax.validation.constraints.PastOrPresent.message = 必须是过去或现在的日期 javax.validation.constraints.Pattern.message = 必须与 "{regexp}" 匹配 javax.validation.constraints.Positive.message = 必须大于 0 javax.validation.constraints.PositiveOrZero.message = 必须大于或等于 0 javax.validation.constraints.Size.message = 大小必须在 {min} 和 {max} 之间 org.hibernate.validator.constraints.CreditCardNumber.message = 无效信用卡卡号 org.hibernate.validator.constraints.Currency.message = 无效货币(必须为 {value} 之一) org.hibernate.validator.constraints.EAN.message = 无效 {type} 条形码 org.hibernate.validator.constraints.Email.message = 电子邮件地址格式不规范 org.hibernate.validator.constraints.ISBN.message = 无效 ISBN org.hibernate.validator.constraints.Length.message = 长度必须介于 {min} 与 {max} 之间 org.hibernate.validator.constraints.CodePointLength.message = 长度必须介于 {min} 与 {max} 之间 org.hibernate.validator.constraints.LuhnCheck.message = ${validatedValue} 的校验位无效,Luhn Modulo 10 校验和失败 org.hibernate.validator.constraints.Mod10Check.message = ${validatedValue} 的校验位无效,Modulo 10 校验和失败 org.hibernate.validator.constraints.Mod11Check.message = ${validatedValue} 的校验位无效,Modulo 11 校验和失败 org.hibernate.validator.constraints.ModCheck.message = ${validatedValue} 的校验位无效,{modType} 校验和失败 org.hibernate.validator.constraints.NotBlank.message = 可能不为空 org.hibernate.validator.constraints.NotEmpty.message = 可能不为空 org.hibernate.validator.constraints.ParametersScriptAssert.message = 脚本表达式 "{script}" 未求值为 true org.hibernate.validator.constraints.Range.message = 必须介于 {min} 与 {max} 之间 org.hibernate.validator.constraints.SafeHtml.message = 可能具有不安全的 HTML 内容 org.hibernate.validator.constraints.ScriptAssert.message = 脚本表达式 "{script}" 未求值为 true org.hibernate.validator.constraints.UniqueElements.message = 必须仅包含唯一元素 org.hibernate.validator.constraints.URL.message = 必须为有效 URL org.hibernate.validator.constraints.br.CNPJ.message = 无效巴西企业纳税人登记号 (CNPJ) org.hibernate.validator.constraints.br.CPF.message = 无效巴西个人纳税人登记号 (CPF) org.hibernate.validator.constraints.br.TituloEleitoral.message = 无效巴西投票人身份证号 org.hibernate.validator.constraints.pl.REGON.message = 无效波兰纳税人识别号 (REGON) org.hibernate.validator.constraints.pl.NIP.message = 无效 VAT 识别号 (NIP) org.hibernate.validator.constraints.pl.PESEL.message = 无效波兰身份证号 (PESEL) org.hibernate.validator.constraints.time.DurationMax.message = 必须短于 ${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'} org.hibernate.validator.constraints.time.DurationMin.message = 必须长于 ${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
使用 1.导包 1 2 3 4 5 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.6.7</version> </dependency>
2.添加注解
在需要校验的字段上添加相应的注解进行校验,其中message是当校验不合法是提示的错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Data @TableName("pms_brand") public class BrandEntity implements Serializable { private static final long serialVersionUID = 1L ; @TableId private Long brandId; @NotBlank(message = "品牌名称不能为空") private String name; @NotEmpty @URL(message = "logo必须是一个合法的URL地址") private String logo; private String descript; private Integer showStatus; @NotEmpty @Pattern(regexp = "/~[a-zA-Z]$/",message = "检索首字母必须是一个字母") private String firstLetter; @NotNull @Min(value = 0,message = "排序必须大于0") private Integer sort; }
3.Cotroller开启校验
使用@Valid注解开启校验,其中参数BindingResult可以获取校验的结果。
BindingResult:BindingResult扩展了Errors接口,同时可以获取数据绑定结果对象的信息。@Valid和BindingResult参数是成对出现的,并且在形参中出现的顺序是固定的,一前一后。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RequestMapping("/save") public R save (@Valid @RequestBody BrandEntity brand, BindingResult result) { if (result.hasErrors()) { Map<String,String> map=new HashMap <>(); result.getFieldErrors().forEach((item)->{ String message=item.getDefaultMessage(); String field = item.getField(); map.put(field,message); }); return R.error(400 ,"提交数据不合法" ).put("data" ,map); }else { brandService.save(brand); } return R.ok(); }
自定义校验
编写一个自定义校验注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Documented @Constraint( validatedBy = {ListValueConstraintValidator.class} ) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface ListValue { String message () default "{com.zhang.common.valid.ListValue.message}" ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; int [] vals() default {}; }
编写一个自定义的校验器 ConstrainValidator
实现ConstraintValidator<Annotation, T>接口,其中Annotation表示自定义的校验注解,T表示校验对象的类型;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ListValueConstraintValidator implements ConstraintValidator <ListValue,Integer>{ private Set<Integer> set=new HashSet <>(); @Override public void initialize (ListValue constraintAnnotation) { int [] vals= constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } @Override public boolean isValid (Integer value, ConstraintValidatorContext constraintValidatorContext) { return set.contains(value); } }
关联自定义的校验器和自定义的校验注解
重点
@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。@Valid不提供分组功能
@Valid和@Validated的区别
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring的JSR-303规范,是标准JSR-303的一个变种),Javax提供了@Valid
(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
@Valid
属于Javax.validation包下,是jdk给提供的,是使用Hibernate validation的时候使用(java的JSR303声明了@Valid这类接口,而Hibernate-validator对其进行了实现)
@Validated
是org.springframework.validation.annotation包下的,是spring提供的,是只用Spring validator校验机制使用
在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
1.分组
1 2 @Validated :提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制@Valid :作为标准的JSR-303 规范,还没有吸收分组的功能
@Validated
会有分组的概念,后面默认是有一个Default.class,当你的@Validated后面没有加任何校验分组信息的时候默认会加Default分组,而对于被校验的对象的属性字段,如果你在属性的校验标签里面没有指定分组会添加到默认分组Default里面。所以如果你的对象里面的每个属性都指定了分组信息,而接口上面并没有添加分组信息会出现@Validated“失效的情况”,实际是因为它校验的是Default分组,而没有字段属于Default分组。上述校验不管加不加@ReqeustBody都能完成校验,但是都不能完成嵌套的校验。
特殊用法:
分组: 当一个实体类需要多种验证方式时,例:对于一个实体类的id来说,新增的时候是不需要的,对于更新时是必须的, 可以通过groups对验证进行分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public interface AllFiled { }public interface First { }public interface Secend { }public class UserInfo { private Integer uid; @NotBlank(message = "姓名不能为空", groups = {First.class, AllFiled.class}) private String username; @NotEmpty(message = "密码不能为空", groups = {AllFiled.class}) @Length(min = 6, max = 10, message = "密码长度只能在6-10之间", groups = {AllFiled.class}) private String password; @Min(value = 18, message = "年龄最小为18", groups = {AllFiled.class}) @Max(value = 60, message = "年龄最大为60", groups = {AllFiled.class}) int age; @AssertTrue(message = "必须同意条款", groups = {Secend.class, AllFiled.class}) boolean agree; } @ResponseBody @GetMapping("validate") public Map<String, String> validate (@Validated({AllFiled.class}) UserInfo userInfo, BindingResult result) { Map<String, String> map = new HashMap <String, String>(); if (result.hasErrors()) { List<ObjectError> list = result.getAllErrors(); for (ObjectError error : list) { FieldError fieldError = (FieldError)error; String defaultMessage = fieldError.getDefaultMessage(); String field = fieldError.getField(); map.put(field, defaultMessage); } } return map; } }
当@Validated分组为空时, 只会验证没有分组的属性, 如上面的uid;
当@Validated{First.class}时, 只会验证分组为First.class的字段, 对于其他分组字段和未分组字段都为空
注意 添加全局异常处理时Controller层参数中不能加BindingResult
参数,否则无法捕获异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Slf4j @RestControllerAdvice(basePackages = "com.zhang.gulimall.product.controller") public class GulimallExceptionControllerAdvice { @ExceptionHandler(value = MethodArgumentNotValidException.class) public R handlerValidException (MethodArgumentNotValidException e) { System.out.println("校验异常处理" ); log.error("数据校验出现问题{},异常类型{}" ,e.getMessage(),e.getClass()); BindingResult bindingResult = e.getBindingResult(); Map<String,String> errorMap=new HashMap <>(); bindingResult.getFieldErrors().forEach((filedError)->{ errorMap.put(filedError.getField(),filedError.getDefaultMessage()); }); return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data" ,errorMap); } @ExceptionHandler(value = RuntimeException.class) public R hadleException () { System.out.println("其他异常" ); return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMsg()); } }