Java-Bean Validation
JSR-303 Java Bean Validation 学习笔记
JSR303 规范(Bean Validation 规范)提供了对 Java EE 和 Java SE 中的 Java Bean 进行验证的方式。该规范主要使用注解的方式来实现对 Java Bean 的验证功能
Bean Validation 规范对约束的定义包括两部分,一是约束注解,类似 @NotNull
就是约束注解;二是约束验证器,每一个约束注解都存在对应的约束验证器,约束验证器用来验证具体的 Java Bean 是否满足该约束注解声明的条件。
@Validated 和 @Valid 区别
@Validated
Spring’s JSR-303 规范,是标准 JSR-303 的一个变种@Valid
标准 JSR-303 规范
@Validated 提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
@Validated 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid 可以用在方法、构造函数、方法参数和成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。
@Valid 支持嵌套验证
public class A {
@NotBlank
private String name;
@Valid
@NotNull
private B b;
}
public class B {
@NotEmpty
private List<String> list;
}
@PostMapping("/api/v1/a/create")
Response createA(@Validated A a);
如果 A 实体的 b 属性不额外加注解,无论入参采用 @Validated 还是 @Valid 验证,Spring Validation 框架只会对 A 的直接属性字段 name 和 b 做验证,不会对 b 中的 list 进行验证,也就是 @Validated 和 @Valid 加在方法参数前,都不会自动对参数进行嵌套验证。
为了能够进行嵌套验证,必须手动在 b 字段上明确指出这个字段里面的实体也要进行验证。 @Validated 不能用在成员属性(字段)上,所以只能用 @Valid
@Validated 或 @Valid 不起作用
需要添加 hibernate-validator 依赖,否则虽然有 @Validated @Valid @NotEmpty 等注解,但没有实际校验的验证器,所以不起作用。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
No validator could be found for constraint NotEmpty
一、现象:
用 jakarta validation-api 中的 @NotEmpty
import javax.validation.constraints.NotEmpty;
@NotEmpty(message = "id列表不能为空")
private List<String> ids;
报错:
11:46:48.051 [http-nio-8768-exec-6] ERROR c.b.c.p.m.e.ExceptionControllerAdvice - unknown error
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'javax.validation.constraints.NotEmpty' validating type 'java.util.List<java.lang.String>'. Check configuration for 'ids'
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.throwExceptionForNullValidator(ConstraintTree.java:229)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorNoUnwrapping(ConstraintTree.java:310)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorInstanceForAutomaticUnwrapping(ConstraintTree.java:244)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:163)
改为 hibernate 的 @NotEmpty
则没问题
import org.hibernate.validator.constraints.NotEmpty;
二、原因:
hibernate-validator 版本冲突 mvn dependency:tree
看到同时引入了两个版本的 hibernate-validator
org.hibernate.validator:hibernate-validator:jar:6.0.18.Final:compile
org.hibernate:hibernate-validator:jar:5.2.5.Final:compile
三、解决:
排除掉低版本的 hibernate-validator 即可,比如 masikkk-app1 中引入了 5.2.5.Final 版本的 hibernate-validator:
<dependency>
<groupId>group.masikkk</groupId>
<artifactId>masikkk-app1</artifactId>
<version>1.0.1</version>
<exclusions>
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
@NotEmpty不起作用
比如写一个mapper,不希望入参集合 userIds 为空,可以加上 @NotEmpty
注解,但一定要在类上加 @Validated
注解,否则不起作用。
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.validation.annotation.Validated;
@Validated
public interface BaseUserMapper {
// 根据User ID列表查询用户列表
@Select("<script>"
+ "SELECT * FROM user WHERE user_id IN "
+ " <foreach collection = 'user_ids' item = 'user_id' open = '(' separator = ',' close = ')'>"
+ " #{user_id}"
+ " </foreach>"
+ "</script>")
List<User> queryUsersByUserIds(@Param("user_ids") @NotEmpty(message = "Param 'userIds' can not be empty.") List<Long> userIds);
}
或者接口中的参数校验,也要加上 @Validated
@PostMapping("/message/publish")
BaseApiResponse publish(@RequestBody @Validated MessageVO messageVO);
原因:没有使用 @Validated
或者 @Valid
注解,原因如下:对 JavaBean 的属性值进行校验前会首先判断是否存在 @Validated 或者 @Valid 注解,只有存在才会执行具体的校验逻辑;
约束注解
JSR303 规范默认提供了几种约束注解的定义
约束注解名称 | 约束注解说明 |
---|---|
@Null | 验证对象是否为空 |
@NotNull | 验证对象是否为非空 |
@AssertTrue | 验证 Boolean 对象是否为 true |
@AssertFalse | 验证 Boolean 对象是否为 false |
@Min | 验证 Number 和 String 对象是否大等于指定的值 |
@Max | 验证 Number 和 String 对象是否小等于指定的值 |
@DecimalMin | 验证 Number 和 String 对象是否大等于指定的值,小数存在精度 |
@DecimalMax | 验证 Number 和 String 对象是否小等于指定的值,小数存在精度 |
@Size | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Digits | 验证 Number 和 String 的构成是否合法 |
@Past | 验证 Date 和 Calendar 对象是否在当前时间之前 |
@Future | 验证 Date 和 Calendar 对象是否在当前时间之后 |
@Pattern | 验证 String 对象是否符合正则表达式的规则 |
自定义约束注解
一个典型的约束注解的定义应该至少包括如下内容:
@Target({ }) // 约束注解应用的目标元素类型
@Retention() // 约束注解应用的时机
@Constraint(validatedBy = {}) // 与约束注解关联的验证器
public @interface ConstraintName{
String message() default " "; // 约束注解验证时的输出消息
Class<?>[] groups() default { }; // 约束注解在验证时所属的组别
Class<? extends Payload>[] payload() default { }; // 约束注解的有效负载
}
比如验证字符串非空的约束注解 @NotEmpty 的定义如下:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {NotEmptyValidator.class})
public @interface NotEmpty {
String message() default "this string may be empty";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
为什么很多约束注解的@Constraint字段都是空的?
Hibernate 的 @NotEmpty 约束注解源码如下,为什么 @Constraint 中是空的呢?如果是空的,如何找到对应的约束验证器类呢?
答案是 org.hibernate.validator.internal.metadata.core.ConstraintHelper.java
中指定约束注解和验证器类的对应关系。
package org.hibernate.validator.constraints;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
@Documented
@Constraint(
validatedBy = {}
)
@SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
@NotNull
@Size(
min = 1
)
public @interface NotEmpty {
String message() default "{org.hibernate.validator.constraints.NotEmpty.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotEmpty[] value();
}
}
约束验证器
约束注解定义完成后,需要同时实现与该约束注解关联的验证器。约束验证器的实现需要扩展 JSR-303 规范提供的接口 javax.validation.ConstraintValidator
:
public interface ConstraintValidator<A extends Annotation, T> {
void initialize(A constraintAnnotation);
boolean isValid(T value, ConstraintValidatorContext context);
}
该接口有两个方法,
方法 initialize 对验证器进行实例化,它必须在验证器的实例在使用之前被调用,并保证正确初始化验证器,它的参数是约束注解;
方法 isValid 是进行约束验证的主体方法,其中 value 参数代表需要验证的实例,context 参数代表约束执行的上下文环境。
字符串非空约束注解 @NotEmpty 对应的验证器的实现如下:
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String>{
public void initialize(NotEmpty parameters) {}
public boolean isValid(String string, ConstraintValidatorContext constraintValidatorContext) {
if (string == null)
return false;
else if(string.length()<1)
return false;
else
return true;
}
}
Bean Validation 技术规范特性概述(IBM高质量文章,介绍的非常详细、全面、准确、易懂)
https://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/index.html
JSR 303 - Bean Validation 介绍及最佳实践
https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html
javax.validation:validation-api
javax 自带 bean validation
@Pattern 正则验证
@Pattern(regexp = "\\w*[a-zA-Z_]+\\w*", message = "名称可由大小写字母、数字和下划线组成,但不能全为数字")
private String fieldName;
@Pattern(regexp = "[\u4e00-\u9fa5a-zA-Z0-9]+", message = "名称不能包含中英文/数字之外的字符")
@Digits 验证数字
只能注解数字类型,包括 BigDecimal
, BigInteger
, CharSequence
, byte
, short
, int
, long
及其包装类型。
integer 小数点前整数部分的最多位数
fraction 小数点后小数部分的做多位数
@Digits(integer = 4, fraction = 0, message = "limit should be integer and less then 1000.")
@Min(value = 0, message = "limit should goe 0.")
private Integer limit;
Java for Web学习笔记(七八):Validation(2)验证标记
https://blog.csdn.net/flowingflying/article/details/78150015
groups 分组验证
所有 javax.validation.constraints 中的约束都有 groups 属性
如果未指定该参数,那么校验都属于 javax.validation.groups.Default 分组。
Validator::validator 方法未指定分组时,相当于使用 javax.validation.groups.Default 分组。
指定分组之后,只会执行 groups = BetaGroup.class 注解的校验:
violationSet=validator.validate(wow, BetaGroup.class);
可以一次指定多个分组的校验:
validator.validate(wow, Default.class, BetaGroup.class, OtherGroup.class);
Hibernate Validator
Hibernate Validator 4.0 是对 JSR-303 规范的实现
Hibernate Validator 附加的约束注解:
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@Range 被注释的元素必须在合适的范围内
@NotEmpty 用在集合类上面,加了@NotEmpty 的 String 类、Collection、Map、数组,是不能为null并且长度必须大于0的
@NotBlank 用于String类型
添加Maven依赖
validation-api 是 javax 规范包。
hibernate-validator 是 hibernate 提供的实现包。
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.1.Final</version>
</dependency>
Spring MVC之@Valid校验
https://my.oschina.net/manmao/blog/749829
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: