我们在业务中经常会遇到参数校验问题,比如前端参数校验、Kafka消息参数校验等,如果业务逻辑比较复杂,各种实体比较多的时候,我们通过代码对这些数据一一校验,会出现大量的重复代码以及和主要业务无关的逻辑。Spring MVC提供了参数校验机制,但是其底层还是通过Hibernate进行数据校验,所以有必要去了解一下Hibernate数据校验和JSR数据校验规范。
JSR数据校验规范
Java官方先后发布了JSR303与JSR349提出了数据合法性校验提供的标准框架:BeanValidator,BeanValidator框架中,用户通过在Bean的属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
JSR注解列表
JSR标准中的数据校验注解如下所示:
JSR注解内容
我们以常用的比较简单的@NotNull注解为例,看看注解中都包含那些内容,如下边的源码所示,可以看到@NotNull注解包含以下几个内容:
1.message:错误消息,示例中的是错误码,可以根据国际化翻译成不同的语言。
2.groups: 分组校验,不同的分组可以有不同的校验条件,比如同一个DTO用于create和update时校验条件可能不一样。
3. payload:BeanValidation API的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用.
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = {
})
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
/**
* Defines several {@link NotNull} annotations on the same element.
*/
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
错误消息message、分组group这些功能我们程序中使用比较多,在我介绍Spring Validator数据校验的文章中有详细说明,但是关于payload我们接触的比较少,下面我们举例说明以下payload的使用,下面的示例中,我们用payload来标识数据校验失败的严重性,通过以下代码。在校验完一个ContactDetails的示例之后, 你就可以通过调用ConstraintViolation.getConstraintDescriptor().getPayload()来得到之前指定到错误级别了,并且可以根据这个信息来决定接下来到行为.
public class Severity {
public static class Info extends Payload {
};
public static class Error extends Payload {
};
}
public class ContactDetails {
@NotNull(message="Name is mandatory", payload=Severity.Error.class)
private String name;
@NotNull(message="Phone number not specified, but not mandatory", payload=Severity.Info.class)
private String phoneNumber;
// ...
}
JSR校验接口
通过前面的JSR校验注解,我们可以给某个类的对应字段添加校验条件,那么怎么去校验这些校验条件呢?JSR进行数据校验的核心接口是Validation,该接口的定义如下所示,我们使用比较多的接口应该是 Set<ConstraintViolation> validate(T object, Class<?>… groups);,该方法可以用于校验某个Object是否符合指定分组的校验规则,如果不指定分组,那么只有默认分组的校验规则会生效。
public interface Validator {
/**
* Validates all constraints on {@code object}.
*/
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
/**
* Validates all constraints placed on the property of {@code object}
* named {@code propertyName}.
*/
<T> Set