【Spring Boot】数据校验
2. 数据校验
对于应用系统而言,任何客户端传入的数据都不是绝对安全有效的,这就要求我们在服务端接收到数据时也对数据的有效性进行验证,以确保传入的数据安全正确。数据校验是Web开发中的重要部分,也是必须考虑和面对的事情。应用系统必须通过某种手段来确保输入的数据从语义上来讲是正确的。
目前数据校验的规范、组件非常多,有JSR-303/JSR-349、HibernateValidator、Spring Validation。
1. Hibernate Validator
JSR(Java Specification Request)规范是Java EE 6中的一项子规范,也叫作Bean Validation。它指定了一整套基于bean的验证API,通过标注给对象属性添加约束条件。Hibernate Validator是对JSR规范的实现,并增加了一些其他校验注解,如@Email、@Length、@Range等。Spring Validation是Spring为了给开发者提供便捷,对Hibernate Validator进行了二次封装。同时,Spring Validation在SpringMVC模块中添加了自动校验,并将校验信息封装进了特定的类中。
所以,JSR定义了数据验证规范,而Hibernate Validator则是基于JSR规范,实现了各种数据验证的注解以及一些附加的约束注解。Spring Validation则是对Hibernate Validator的封装整合。
包含了Hibernate Validator实现的JSR-303定义的验证注解和HibernateValidator自己定义的验证注解,同时也支持自定义约束注解。所有的注解都包含code和message这两个属性。
- Message定义数据校验不通过时的错误提示信息。
- code定义错误的类型。
使用Hibernate Validator校验数据需要定义一个接收的数据模型,使用注解的形式描述字段校验的规则。
2. JavaBean参数校验
Post请求参数较多时,可以在对应的数据模型(Java Bean)中进行数据校验,通过注解来指定字段校验的规则。
首先引入Validator依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.20.Final</version>
</dependency>
我们创建一个JavaBean 实体类
public class User {
@NotBlank(message = "姓名不允许为空")
@Length(min = 2,max = 10,message = "姓名长度错误,长度长度2-10!")
private String name;
@NotNull(message = "年龄不能为空")
@Min(18,message = "年齡最小不能小于18")
private int age;
@NotBlank(message = "地址不能为空")
private String address;
@Pattern(regexp = "^1(3[0-9]|5[012356789]|7[1235678]|8[0-9])\\d{8}$", message ="手机号格式错误")
private String phone;
@Email(message = "邮箱格式错误")
private String email;
//省略get、set方法
}
在实体类中每个注解的属性message是数据校验不通过时要给出的提示信息。
在controller层进行校验。
@PostMapping(path = "/check")
public String check(@RequestBody @Valid User user, BindingResult result) {
String name = user.getName();
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
System.out.println(error.getCode() + "_" + error.getDefaultMessage());
}
}
return name;
}
我们在@RequestBody后面加上@Valid注解标识需要进行校验。
用postman进行测试,测试结果如下。
3. URL参数校验
一般GET请求都是在URL中传入参数。对于这种情况,可以直接通过注解来指定参数的校验规则。
@RequestMapping("/test")
public String query(@Length(min = 2, max = 10, message = "姓名长度错误,姓名长度2-10!") String name
, @Min(value = 1, message = "年龄最小只能1")
@Max(value = 99, message = "年龄最大只能99")
@RequestParam(name = "age", required = true) int age) {
System.out.println(name + "," + age);
return name + "," + age;
}
我们发现捕捉到了错误。
在上面的示例中,使用@Min、@Max等注解对URL中传入的参数进行校验。需要注意的是,使用@Valid注解是无效的,需要在方法所在的控制器上添加@Validated注解来使得验证生效。
@RestController
@Validated
public class TestController {}
4. JavaBean 对象级联校验
对于JavaBean对象中的普通属性字段,我们可以直接使用注解进行数据校验。在属性上添加@Valid注解就可以作为属性对象的内部属性进行验证(验证User对象,可以验证UserDetail的字段)。
public class User {
@NotBlank(message = "姓名不允许为空")
@Length(min = 2, max = 10, message = "姓名长度错误,长度长度2-10!")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 18,message = "年齡最小不能小于18")
// @CustomAgeValidator
private int age;
@NotBlank(message = "地址不能为空")
private String address;
@Pattern(regexp = "^1(3[0-9]|5[012356789]|7[1235678]|8[0-9])\\d{8}$", message = "手机号格式错误")
private String phone;
@Email(message = "邮箱格式错误")
private String email;
@NotNull
@Valid
private UserDetail userDetail;
//省略get、set方法
}
public class UserDetail {
@Length(min = 5,max = 17,message = "length长度在[5,17]之间")
@NotNull(message = "额外的不能为空")
private String extField;
//省略get、set方法
}
在属性上添加@Valid就可以对User中的关联对象UserDetail的字段进行数据校验。
测试结果如下
5.分组校验
在不同情况下,可能对JavaBean对象的数据校验规则有所不同,有时需要根据数据状态对JavaBean中的某些属性字段进行单独验证。这时就可以使用分组校验功能,即根据状态启用一组约束。Hibernate Validator的注解提供了groups参数,用于指定分组,如果没有指定groups参数,则默认属于javax.validation.groups.Default分组。
首先我们创建GroupA和GroupB接口
public interface GroupA {
}
public interface GroupB {
}
然后我们创建实体类Person,并在相关的字段中定义校验分组规则。
public class Person {
private Integer userId;
private String name;
@Max(value = 40,message = "最大為40",groups = {GroupB.class})
@Min(value = 30,message = "最小為30",groups = {GroupB.class})
@Min(value = 20,message = "最小為20",groups = {GroupA.class})
@Max(value = 30,message = "最大為30",groups = {GroupA.class})
private Integer age;
//省略get和set方法
}
在上面我们对age进行了分组校验,GroupA的规则为20 ~ 30,GroupB的规则为30 ~ 40。
最后使用分组校验。
@RequestMapping("/save3")
public String save(@RequestBody @Validated({GroupA.class, Default.class})Person person,BindingResult result){
System.out.println(JSON.toJSONString(result.getAllErrors()));
return "success";
}
[{"arguments":[{"code":"age","codes":["person.age","age"],"defaultMessage":"age"},20],"bindingFailure":false,"code":"Min","codes":["Min.person.age","Min.age","Min.java.lang.Integer","Min"],"defaultMessage":"最小為20","field":"age","objectName":"person","rejectedValue":2}]
在报错信息中我们发现结果最小为20,但是我们传过来的值只有2。
6. 声明自定义校验注解
首先,我们定义新的校验注解@CustomAgeValidator
@Min(value = 18, message = "年龄最小不能小于18")
@Max(value = 120, message = "年龄最大不能超过120")
@Constraint(validatedBy = {})//不指定校验器
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAgeValidator {
String message() default "年龄大小必须大于18并且小于120";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
在创建完自定义注解后,我们在User类的age属性上使用自定义组合注解。
public class User {
@NotBlank(message = "姓名不允许为空")
@Length(min = 2, max = 10, message = "姓名长度错误,长度长度2-10!")
private String name;
// @NotNull(message = "年龄不能为空")
// @Min(value = 18,message = "年齡最小不能小于18")
@CustomAgeValidator
private int age;
@NotBlank(message = "地址不能为空")
private String address;
@Pattern(regexp = "^1(3[0-9]|5[012356789]|7[1235678]|8[0-9])\\d{8}$", message = "手机号格式错误")
private String phone;
@Email(message = "邮箱格式错误")
private String email;
//省略get和set注解
}
在上面user类中,我们在age上添加了@CustomAgeValidator自定义注解,这样age字段就会使用我们自定义的校验规则。