当前位置 : 首页 » 文章分类 :  开发  »  Java-Bean Validation

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


上一篇 Apache-Commons-Lang 使用笔记

下一篇 Spring-Scheduling

阅读
评论
2.6k
阅读预计11分钟
创建日期 2018-06-28
修改日期 2021-09-29
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论