概述
在 Web 应用中,客户端提交数据之前都会进行数据的校验,比如用户注册时填写的邮箱地址是否符合规范、用户名长度的限制等等,不过这并不意味着服务端的代码可以免去数据验证的工作,用户也可能使用 HTTP 工具直接发送违法数据。为了保证数据的安全性,服务端的数据校验是必须的。
先理清概念:
- JSR-303 是 JavaEE 6 中的一项子规范,又称作 Bean Validation,提供了针对 Java Bean 字段的一些校验注解,如
@NotNull
,@Min
等。JSR-349 是其升级版本,添加了一些新特性。 - Hibernate Validator 是对这个规范的实现(与 ORM 框架无关),并在它的基础上增加了一些新的校验注解。
- Spring 本身也有一个校验接口
Validator
,位于 org.springframework.validation 包下,但是使用这个接口需要进行硬编码,也就是手动校验,没有提供注解进行简化。为了给开发者提供便捷,Spring 也全面支持 JSR-303、JSR-349 规范,对 Hibernate Validation 进行二次封装,在 SpringMVC 模块中添加了自动校验机制,可以利用注解对 Java Bean 的字段的值进行校验,并将校验信息封装进特定的类中。
下面将介绍如何在 Spring 应用中使用 JSR-303 校验规范。
校验注解
1. JSR-303 包含的注解
注解名称 | 说明 |
---|---|
@Null | 被注解元素必须为 null |
@NonNull | 被注解元素必须不为 null |
@AssertTrue | 被注解元素必须为 true |
@AssertFalse | 被注解元素必须为 false |
@Min(value) | 被注解元素必须是一个值,并且不能小于指定的值 |
@Max(value) | 被注解元素必须是一个值,并且不能大于指定的值 |
@DecimalMin(value) | 被注解元素必须是一个数字,并且不能小于指定的值 |
@DecimalMax(value) | 被注解元素必须是一个数字,并且不能大于指定的值 |
@Size(max=,min=) | 被注解元素的大小必须在指定范围内 |
@Digits(integer,fraction) | 被注解元素必须是一个数字,其值必须在指定范围内 |
@Past | 被注解元素必须是一个过去的日期 |
@Future | 被注解元素必须是一个将来的日期 |
@Pattern(regex=,flag=) | 被注解元素必须符合指定的正则表达式 |
2. Hibernate Validator 扩展的注解
注解名称 | 说明 |
---|---|
@NotBlank(message=) | 被注解的字符串必须非 null 且trim() 后长度大于 0 |
被注解元素必须是电子邮箱地址 | |
@Length(min=,max=) | 被注解的字符串的长度必须在指定范围内 |
@NotEmpty | 被注解元素(字符串、数组、集合等)必须非 null 且长度大于 0 |
@Range(min=,max=,message=) | 被注解元素必须在合适的范围内 |
@URL | 被注解元素必须是合法的 URL |
用法
1. 依赖
在pom.xml
中引入 Hibernate Validator 需要的依赖包:
如果你使用的是 SpringBoot,那么只需引入spring-boot-starter-web
即可,它的子依赖中包含了hibernate-validator
和必要的数据绑定组件。
2. 实体类
public class Student { private Long id; @NotBlank(message = "名称不能为空") private String name; @Range(min = 10, max = 25, message = "年龄必须在10~25之间 ") private Integer age; @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone; @Email(message = "邮箱格式错误") private String email; // Getter/Setter }
每一个注解都包含了message
字段,用于校验失败时作为提示信息。
3. 处理器方法
在处理器方法需要校验的参数上添加@Valid
注解,就可以“激活”对它的校验操作,后面传入一个 BindingResult 类型的参数,用于获取校验失败情况下的反馈信息。
@Valid
注解的参数和 BindingResult 参数必须是成对出现的,并且一前一后。
自定义校验
假如现在有一个需求是学生名字的内部不能含有空格,我们要如何自定义一个满足该要求的校验呢?
1. 自定义注解
@Target({FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) @Constraint(validatedBy = NotContainSpaceValidator.class) public @interface NotContainSpace { //默认错误消息 String message() default "不能包含空格"; //分组 Class<?>[] groups() default {}; //负载 Class<? extends Payload>[] payload() default {}; //指定多个时使用,从而支持重复注解 @Target({FIELD, METHOD, PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { NotContainSpace[] value(); } }
@Constraint
指定这个注解真正的校验者类。
2. 校验者类
实现ConstraintValidator
接口:
第一个泛型参数是表明校验的注解类型,第二个泛型参数是需要被校验的类型。
- initialize:初始化事件方法
- isValid:判断是否合法的方法
ConstraintValidatorContext
这个上下文包含了校验中所有的信息,我们可以利用这个对象进行获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作。
4. 使用
@NotContainSpace private String name;
可以看到NotContainSpaceValidator
的执行是先于处理器方法的,并且禁用默认提示语、设置新的提示语等操作都是有效的。
分组校验
如果对同一个类,在不同的使用场景下有不同的校验规则,就可以使用分组校验。比如更新一条学生记录时,要求 id 不为 null 且大于 0:
我们上面在对方法参数添加自动校验时用的是@Valid
注解,它是由 javax 提供的,其实 Spring Validation 校验框架还提供了@Validated
注解。在检验 Controller 的入参是否符合规范时,它们的基本验证功能并没有多大区别。但是,现在要进行分组校验,就必须使用@Validated:
不过注意,在@Validated
中指定了分组,那么其他未分组的校验将会被忽略。
关于更多 @Valid 和 @Validated 的区别,可见文末参考链接。
END